Skip to content

Commit cfb3940

Browse files
authored
Merge branch 'dev' into 977
2 parents 0b07492 + 666dff1 commit cfb3940

17 files changed

Lines changed: 277 additions & 311 deletions

File tree

src/appmixer/facebook/auth.js

Lines changed: 60 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
2-
const graph = require('fbgraph');
2+
3+
const API_VERSION = 'v22.0';
34

45
module.exports = {
56

@@ -11,104 +12,81 @@ module.exports = {
1112

1213
authUrl: context => {
1314

14-
return graph.getOauthUrl({
15+
const params = {
1516
'client_id': context.clientId,
1617
'redirect_uri': context.callbackUrl,
1718
'scope': context.scope.join(', '),
1819
'state': context.ticket
19-
});
20+
};
21+
return `https://www.facebook.com/${API_VERSION}/dialog/oauth?` + new URLSearchParams(params).toString();
2022
},
2123

22-
requestAccessToken: context => {
23-
24-
return new Promise((resolve, reject) => {
25-
graph.authorize({
26-
'client_id': context.clientId,
27-
'redirect_uri': context.callbackUrl,
28-
'client_secret': context.clientSecret,
29-
'code': context.authorizationCode
30-
}, (error, result) => {
31-
if (error) {
32-
return reject(error);
33-
}
34-
let newDate = new Date();
35-
newDate.setTime(newDate.getTime() + (result['expires_in'] * 1000));
36-
resolve({
37-
accessToken: result['access_token'],
38-
accessTokenExpDate: newDate
39-
});
40-
});
41-
});
24+
requestAccessToken: async context => {
25+
26+
const params = {
27+
'client_id': context.clientId,
28+
'redirect_uri': context.callbackUrl,
29+
'client_secret': context.clientSecret,
30+
'code': context.authorizationCode
31+
};
32+
33+
const url = `https://graph.facebook.com/${API_VERSION}/oauth/access_token`;
34+
const response = await context.httpRequest.get(url + '?' + new URLSearchParams(params).toString());
35+
const newDate = new Date();
36+
newDate.setTime(newDate.getTime() + (response.data['expires_in'] * 1000));
37+
return {
38+
accessToken: response.data['access_token'],
39+
accessTokenExpDate: newDate
40+
};
4241
},
4342

4443
accountNameFromProfileInfo: context => {
4544

4645
return context.profileInfo['name'] || context.profileInfo['id'].toString();
4746
},
4847

49-
requestProfileInfo: context => {
50-
51-
return new Promise((resolve, reject) => {
52-
graph.setAccessToken(context.accessToken);
53-
graph.setAppSecret(context.clientSecret);
54-
graph.batch([{
55-
method: 'GET',
56-
'relative_url': 'me' // Get the current user's profile information
57-
}], (err, res) => {
58-
if (err) {
59-
return reject(err);
60-
}
61-
try {
62-
resolve(JSON.parse(res[0]['body']));
63-
} catch (error) {
64-
reject(error);
65-
}
66-
});
67-
});
48+
requestProfileInfo: async context => {
49+
50+
const url = `https://graph.facebook.com/${API_VERSION}/me?access_token=${context.accessToken}`;
51+
const response = await context.httpRequest.get(url);
52+
return response.data;
6853
},
6954

70-
refreshAccessToken: context => {
71-
72-
return new Promise((resolve, reject) => {
73-
graph.extendAccessToken({
74-
'access_token': context.accessToken,
75-
'client_id': context.clientId,
76-
'client_secret': context.clientSecret
77-
}, (err, result) => {
78-
if (err) {
79-
if (err.type === 'OAuthException') {
80-
return reject(new context.InvalidTokenError(err.message));
81-
}
82-
return reject(err);
83-
}
84-
let newDate = new Date();
85-
newDate.setTime(newDate.getTime() + (result['expires_in'] * 1000));
86-
resolve({
87-
accessToken: result['access_token'],
88-
accessTokenExpDate: newDate
89-
});
90-
});
91-
});
55+
refreshAccessToken: async context => {
56+
57+
const params = {
58+
'access_token': context.accessToken,
59+
'client_id': context.clientId,
60+
'client_secret': context.clientSecret,
61+
'fb_exchange_token': context.accessToken,
62+
'grant_type': 'fb_exchange_token'
63+
};
64+
65+
const url = `https://graph.facebook.com/${API_VERSION}/oauth/access_token`;
66+
67+
const response = await context.httpRequest.get(url + '?' + new URLSearchParams(params).toString());
68+
if (response.data.error) {
69+
throw new context.InvalidTokenError(response.data.error.message);
70+
}
71+
const newDate = new Date();
72+
newDate.setTime(newDate.getTime() + (response.data['expires_in'] * 1000));
73+
return {
74+
accessToken: response.data['access_token'],
75+
accessTokenExpDate: newDate
76+
};
9277
},
9378

94-
validateAccessToken: context => {
95-
96-
return new Promise((resolve, reject) => {
97-
graph.setAccessToken(context.accessToken);
98-
graph.setAppSecret(context.clientSecret);
99-
graph.batch([{
100-
method: 'GET',
101-
'relative_url': 'me'
102-
}], (err, res) => {
103-
if (err?.type === 'OAuthException') {
104-
return reject(new context.InvalidTokenError(err.message));
105-
}
106-
if (err) {
107-
return reject(err);
108-
}
109-
resolve();
110-
});
111-
});
79+
validateAccessToken: async context => {
80+
81+
try {
82+
const url = `https://graph.facebook.com/${API_VERSION}/me?access_token=${context.accessToken}`;
83+
await context.httpRequest.get(url);
84+
} catch (err) {
85+
if (err.response?.data?.error?.type === 'OAuthException') {
86+
throw new context.InvalidTokenError(err.response.data.error.message);
87+
}
88+
throw err;
89+
}
11290
}
11391
}
11492
};

src/appmixer/facebook/bundle.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
{
22
"name": "appmixer.facebook",
3-
"version": "1.0.1",
4-
"changelog": [
5-
"Initial version"
6-
]
3+
"version": "2.0.0",
4+
"changelog": {
5+
"1.0.0": ["Initial version"],
6+
"1.0.1": ["Minor fixes"],
7+
"2.0.0": [
8+
"Updated to Facebook Graph API v22.0 (from v3.2).",
9+
"Removed deprecated fbgraph and bluebird dependencies — uses native fetch via context.httpRequest.",
10+
"Updated OAuth scopes: manage_pages → pages_manage_posts/pages_read_engagement, publish_pages → pages_manage_posts.",
11+
"Removed FindPlace component (Place Search API was removed from Graph API).",
12+
"Modernized all components to use shared FacebookClient utility."
13+
]
14+
}
715
}
Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,37 @@
11
'use strict';
2-
const graph = require('fbgraph');
3-
const Promise = require('bluebird');
4-
const CursorPaging = require('../../lib').CursorPaging;
52

6-
/**
7-
* Process posts to find newly added.
8-
* @param {Set} knownPosts
9-
* @param {Set} actualPosts
10-
* @param {Set} newPosts
11-
* @param {Object} post
12-
*/
13-
function processPosts(knownPosts, actualPosts, newPosts, post) {
14-
15-
if (knownPosts && !knownPosts.has(post['id'])) {
16-
newPosts.add(post);
17-
}
18-
actualPosts.add(post['id']);
19-
}
3+
const { FacebookClient } = require('../../lib');
204

215
/**
22-
* Component which triggers whenever new post is added
23-
* @extends {Component}
6+
* Component which triggers whenever new post is created on the user's feed.
247
*/
258
module.exports = {
269

2710
async tick(context) {
2811

29-
let since = parseInt((new Date().getTime() / 1000).toFixed(0));
12+
const client = new FacebookClient(context);
13+
const since = Math.floor(Date.now() / 1000);
3014

31-
graph.setVersion('3.2');
32-
let client = graph.setAccessToken(context.auth.accessToken);
33-
let paging = new CursorPaging(Promise.promisify(client.get, { context: client }));
34-
let posts = await paging.fetch(`/me/feed?since=${context.state.since || since}`);
15+
const posts = await client.fetchAll('/me/feed', {
16+
since: context.state.since || since,
17+
fields: 'id,message,story,created_time'
18+
});
3519

36-
let known = Array.isArray(context.state.known) ? new Set(context.state.known) : null;
37-
let actual = new Set();
38-
let diff = new Set();
20+
const known = Array.isArray(context.state.known) ? new Set(context.state.known) : null;
21+
const actual = new Set();
22+
const diff = [];
3923

40-
posts.forEach(processPosts.bind(null, known, actual, diff));
24+
for (const post of posts) {
25+
if (known && !known.has(post.id)) {
26+
diff.push(post);
27+
}
28+
actual.add(post.id);
29+
}
4130

42-
if (diff.size) {
43-
await Promise.map(diff, post => {
44-
return context.sendJson(post, 'post');
45-
});
31+
for (const post of diff) {
32+
await context.sendJson(post, 'post');
4633
}
34+
4735
await context.saveState({ known: Array.from(actual), since });
4836
}
4937
};

src/appmixer/facebook/lib.js

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,65 @@
11
'use strict';
22

3+
const API_VERSION = 'v22.0';
4+
const BASE_URL = `https://graph.facebook.com/${API_VERSION}`;
5+
36
/**
4-
* Cursor based paging for FB api.
7+
* Facebook Graph API client using context.httpRequest.
58
*/
6-
class CursorPaging {
9+
class FacebookClient {
10+
11+
constructor(context) {
12+
this.context = context;
13+
this.accessToken = context.auth.accessToken;
14+
}
15+
16+
async get(path, params = {}) {
17+
params.access_token = this.accessToken;
18+
const url = `${BASE_URL}${path}?` + new URLSearchParams(params).toString();
19+
const response = await this.context.httpRequest.get(url);
20+
return response.data;
21+
}
22+
23+
async post(path, data = {}) {
24+
data.access_token = this.accessToken;
25+
const url = `${BASE_URL}${path}`;
26+
const response = await this.context.httpRequest.post(url, data);
27+
return response.data;
28+
}
729

830
/**
9-
* @param {function} func - get function that will be called with cursor URL
31+
* Set a different access token (e.g. page token).
1032
*/
11-
constructor(func) {
12-
13-
this.func = func;
14-
this.accumulator = [];
33+
setAccessToken(token) {
34+
this.accessToken = token;
35+
return this;
1536
}
1637

1738
/**
18-
* Fetch all records.
19-
* @param {string} next - cursor URL for next page
20-
* @return {Promise<Array[Object]>}
39+
* Fetch all pages using cursor-based pagination.
2140
*/
22-
async fetch(next) {
41+
async fetchAll(path, params = {}) {
42+
const results = [];
43+
let url = `${BASE_URL}${path}`;
44+
params.access_token = this.accessToken;
45+
46+
let response = await this.context.httpRequest.get(url + '?' + new URLSearchParams(params).toString());
47+
let data = response.data;
2348

24-
let res = await this.func(next);
25-
if (res['data'] && res['data'].length) {
26-
this.accumulator = this.accumulator.concat(res['data']);
49+
if (data.data) {
50+
results.push(...data.data);
2751
}
28-
if (res['paging'] && res['paging']['next']) {
29-
return this.fetch(res['paging']['next']);
52+
53+
while (data.paging?.next) {
54+
response = await this.context.httpRequest.get(data.paging.next);
55+
data = response.data;
56+
if (data.data) {
57+
results.push(...data.data);
58+
}
3059
}
31-
return this.accumulator;
60+
61+
return results;
3262
}
3363
}
3464

35-
module.exports.CursorPaging = CursorPaging;
65+
module.exports = { FacebookClient, API_VERSION, BASE_URL };

src/appmixer/facebook/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
{
22
"name": "appmixer.facebook",
33
"version": "0.0.1",
4-
"dependencies": {
5-
"bluebird": "^3.5.3",
6-
"fbgraph": "^1.4.4"
7-
}
4+
"dependencies": {}
85
}

0 commit comments

Comments
 (0)