@meltwater/conversations-api-services 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/.github/workflows/release.yml +4 -6
  2. package/CLAUDE.md +70 -0
  3. package/dist/cjs/data-access/http/facebook.native.js +122 -106
  4. package/dist/cjs/data-access/http/instagram.native.js +92 -49
  5. package/dist/cjs/data-access/http/linkedin.native.js +135 -56
  6. package/dist/cjs/data-access/http/threads.native.js +53 -18
  7. package/dist/cjs/data-access/http/tiktok.native.js +18 -12
  8. package/dist/cjs/data-access/http/twitter.native.js +142 -40
  9. package/dist/cjs/data-access/http/youtube.native.js +19 -13
  10. package/dist/esm/data-access/http/facebook.native.js +123 -107
  11. package/dist/esm/data-access/http/instagram.native.js +93 -50
  12. package/dist/esm/data-access/http/linkedin.native.js +136 -57
  13. package/dist/esm/data-access/http/threads.native.js +54 -19
  14. package/dist/esm/data-access/http/tiktok.native.js +19 -13
  15. package/dist/esm/data-access/http/twitter.native.js +143 -41
  16. package/dist/esm/data-access/http/youtube.native.js +20 -14
  17. package/package.json +1 -3
  18. package/src/data-access/http/README.md +50 -0
  19. package/src/data-access/http/facebook.native.js +122 -144
  20. package/src/data-access/http/instagram.native.js +113 -93
  21. package/src/data-access/http/linkedin.native.js +144 -83
  22. package/src/data-access/http/threads.native.js +58 -27
  23. package/src/data-access/http/tiktok.native.js +19 -39
  24. package/src/data-access/http/twitter.native.js +145 -65
  25. package/src/data-access/http/youtube.native.js +21 -40
  26. package/dist/cjs/lib/hiddenComment.helper.js +0 -119
  27. package/dist/esm/lib/hiddenComment.helper.js +0 -112
  28. package/src/lib/hiddenComment.helper.js +0 -107
@@ -1,6 +1,6 @@
1
1
  import superagent from 'superagent';
2
2
  import { removePrefix } from '../../lib/externalId.helpers.js';
3
- import { loggerDebug, loggerError, loggerInfo, loggerWarn } from '../../lib/logger.helpers.js';
3
+ import { loggerDebug, loggerInfo, loggerWarn, MeltwaterAttributes } from '../../lib/logger.helpers.js';
4
4
  import OAuth from 'oauth-1.0a';
5
5
  import crypto from 'crypto';
6
6
  import axios from 'axios';
@@ -69,7 +69,12 @@ async function makeTwitterV2Request(v2Endpoint, v1FallbackQuery, token, logger)
69
69
  source: 'v1.1'
70
70
  };
71
71
  } catch (fallbackError) {
72
- loggerError(logger, `Both v2 and v1.1 APIs failed for ${v2Endpoint}`, fallbackError);
72
+ loggerInfo(logger, `Failed to call twitter api - makeTwitterV2Request`, {
73
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
74
+ v2Endpoint,
75
+ error: fallbackError?.message
76
+ })
77
+ });
73
78
  }
74
79
  }
75
80
  return {
@@ -119,17 +124,25 @@ function normalizeUsersData(users) {
119
124
  * @returns {Promise<Object>} User data from v2 API
120
125
  */
121
126
  export async function getAuthenticatedUser(token, logger) {
127
+ const apiUrl = 'https://api.x.com/2/users/me';
122
128
  try {
123
129
  const result = await getRequest({
124
130
  token,
125
- uri: 'https://api.x.com/2/users/me',
131
+ uri: apiUrl,
126
132
  attachUrlPrefix: false,
127
133
  convertPayloadToUri: false,
128
134
  logger
129
135
  });
130
136
  return result.data;
131
137
  } catch (e) {
132
- loggerError(logger, `Error getting authenticated user info`, e);
138
+ loggerInfo(logger, `Failed to call twitter api - getAuthenticatedUser`, {
139
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
140
+ apiUrl,
141
+ status: e?.response?.status,
142
+ responseBody: e?.response?.body,
143
+ error: e?.response?.body?.errors?.[0]?.message ?? e?.message
144
+ })
145
+ });
133
146
  throw e;
134
147
  }
135
148
  }
@@ -173,8 +186,12 @@ export async function getUserInfoFromHandles(token, handles, logger) {
173
186
  const normalizedData = normalizeUsersData(result.data, result.source);
174
187
  return normalizedData || [];
175
188
  } catch (e) {
176
- loggerError(logger, `Unexpected error in getUserInfoFromHandles`, e);
177
- return [];
189
+ loggerInfo(logger, `Failed to call twitter api - getUserInfoFromHandles`, {
190
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
191
+ error: e?.message
192
+ })
193
+ });
194
+ throw e;
178
195
  }
179
196
  }
180
197
  export async function getUserInfoFromHandle(token, handleId, logger) {
@@ -196,7 +213,13 @@ export async function getUserInfoFromHandle(token, handleId, logger) {
196
213
  throw result.error || new Error('Failed to get user info from both APIs');
197
214
  }
198
215
  } catch (e) {
199
- loggerError(logger, `Error in getUserInfoFromHandle with handleId: ${JSON.stringify(handleId)}`, e);
216
+ loggerInfo(logger, `Failed to call twitter api - getUserInfoFromHandle`, {
217
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
218
+ handleId,
219
+ error: e?.message
220
+ })
221
+ });
222
+ throw e;
200
223
  }
201
224
  }
202
225
  export async function getDirectMessageImage(token, imageUrl, logger) {
@@ -212,7 +235,12 @@ export async function getDirectMessageImage(token, imageUrl, logger) {
212
235
  contentType: result.contentType
213
236
  };
214
237
  } catch (e) {
215
- loggerError(logger, `Error getting image`, e);
238
+ loggerInfo(logger, `Failed to call twitter api - getDirectMessageImage`, {
239
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
240
+ imageUrl,
241
+ error: e?.message
242
+ })
243
+ });
216
244
  throw e;
217
245
  }
218
246
  }
@@ -232,7 +260,13 @@ export async function getCurrentInfo(token, externalId, logger) {
232
260
  result.data.retweeted = !!result.data.current_user_retweet;
233
261
  return result.data.data[0].public_metrics;
234
262
  } catch (error) {
235
- loggerDebug(logger, 'Error in twitter getCurrentInfo', error);
263
+ loggerInfo(logger, `Failed to call twitter api - getCurrentInfo`, {
264
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
265
+ externalId,
266
+ error: error?.message
267
+ })
268
+ });
269
+ throw error;
236
270
  }
237
271
  }
238
272
  export async function getMentionHandleInfo(token, externalId, logger) {
@@ -247,7 +281,13 @@ export async function getMentionHandleInfo(token, externalId, logger) {
247
281
  });
248
282
  return result.data;
249
283
  } catch (error) {
250
- loggerDebug(logger, 'Error in twitter getMentionHandleInfo', error);
284
+ loggerInfo(logger, `Failed to call twitter api - getMentionHandleInfo`, {
285
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
286
+ externalId,
287
+ error: error?.message
288
+ })
289
+ });
290
+ throw error;
251
291
  }
252
292
  }
253
293
  export async function retweet(token, sourceId, externalId, logger) {
@@ -268,7 +308,14 @@ export async function retweet(token, sourceId, externalId, logger) {
268
308
  return false;
269
309
  }
270
310
  } catch (error) {
271
- loggerDebug(logger, `Error in retweet user: ${sourceId}`, error);
311
+ loggerInfo(logger, `Failed to call twitter api - retweet`, {
312
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
313
+ sourceId,
314
+ externalId,
315
+ error: error?.message
316
+ })
317
+ });
318
+ throw error;
272
319
  }
273
320
  }
274
321
  export async function unRetweet(token, sourceId, externalId, logger) {
@@ -284,15 +331,20 @@ export async function unRetweet(token, sourceId, externalId, logger) {
284
331
  return false;
285
332
  }
286
333
  } catch (error) {
287
- loggerDebug(logger, `Error in unretweet user: ${sourceId}`, error, {
288
- [MeltwaterAttributes.SOCIALEXTERNALID]: externalId
334
+ loggerInfo(logger, `Failed to call twitter api - unRetweet`, {
335
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
336
+ sourceId,
337
+ externalId,
338
+ error: error?.message
339
+ })
289
340
  });
341
+ throw error;
290
342
  }
291
343
  }
292
344
  export async function like(token, externalId, logger) {
345
+ let authenticatedUser = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
293
346
  try {
294
- // Get the authenticated user's ID first
295
- const userInfo = await getAuthenticatedUser(token, logger);
347
+ const userInfo = authenticatedUser ?? (await getAuthenticatedUser(token, logger));
296
348
  const userId = userInfo.data.id;
297
349
  let response = await postRequest({
298
350
  token,
@@ -313,14 +365,19 @@ export async function like(token, externalId, logger) {
313
365
  return false;
314
366
  }
315
367
  } catch (error) {
316
- loggerDebug(logger, `Twitter Error in like user: ${externalId}`, error);
368
+ loggerInfo(logger, `Failed to call twitter api - like`, {
369
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
370
+ externalId,
371
+ error: error?.message
372
+ })
373
+ });
317
374
  throw error;
318
375
  }
319
376
  }
320
377
  export async function unLike(token, externalId, logger) {
378
+ let authenticatedUser = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
321
379
  try {
322
- // Get the authenticated user's ID first
323
- const userInfo = await getAuthenticatedUser(token, logger);
380
+ const userInfo = authenticatedUser ?? (await getAuthenticatedUser(token, logger));
324
381
  const userId = userInfo.data.id;
325
382
  let response = await deleteRequest({
326
383
  token,
@@ -335,14 +392,19 @@ export async function unLike(token, externalId, logger) {
335
392
  return false;
336
393
  }
337
394
  } catch (error) {
338
- loggerDebug(logger, `Error in unlike user: ${externalId}`, error);
395
+ loggerInfo(logger, `Failed to call twitter api - unLike`, {
396
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
397
+ externalId,
398
+ error: error?.message
399
+ })
400
+ });
339
401
  throw error;
340
402
  }
341
403
  }
342
404
  export async function followUser(token, profileId, logger) {
405
+ let authenticatedUser = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
343
406
  try {
344
- // Get the authenticated user's ID first
345
- const userInfo = await getAuthenticatedUser(token, logger);
407
+ const userInfo = authenticatedUser ?? (await getAuthenticatedUser(token, logger));
346
408
  const userId = userInfo.data.id;
347
409
  let response = await postRequest({
348
410
  token,
@@ -360,13 +422,19 @@ export async function followUser(token, profileId, logger) {
360
422
  return false;
361
423
  }
362
424
  } catch (error) {
363
- loggerDebug(logger, `Error in following user: ${profileId}`, error);
425
+ loggerInfo(logger, `Failed to call twitter api - followUser`, {
426
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
427
+ profileId,
428
+ error: error?.message
429
+ })
430
+ });
431
+ throw error;
364
432
  }
365
433
  }
366
434
  export async function unFollowUser(token, profileId, logger) {
435
+ let authenticatedUser = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
367
436
  try {
368
- // Get the authenticated user's ID first
369
- const userInfo = await getAuthenticatedUser(token, logger);
437
+ const userInfo = authenticatedUser ?? (await getAuthenticatedUser(token, logger));
370
438
  const userId = userInfo.data.id;
371
439
  let response = await deleteRequest({
372
440
  token,
@@ -381,13 +449,19 @@ export async function unFollowUser(token, profileId, logger) {
381
449
  return false;
382
450
  }
383
451
  } catch (error) {
384
- loggerDebug(logger, `Error in unfollowing user: ${profileId}`, error);
452
+ loggerInfo(logger, `Failed to call twitter api - unFollowUser`, {
453
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
454
+ profileId,
455
+ error: error?.message
456
+ })
457
+ });
458
+ throw error;
385
459
  }
386
460
  }
387
461
  export async function userFollowStatus(token, profileId, logger) {
462
+ let authenticatedUser = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
388
463
  try {
389
- // Get the authenticated user's ID first
390
- const userInfo = await getAuthenticatedUser(token, logger);
464
+ const userInfo = authenticatedUser ?? (await getAuthenticatedUser(token, logger));
391
465
  const userId = userInfo.data.id;
392
466
 
393
467
  // Check if authenticated user is following the target user
@@ -419,7 +493,7 @@ export async function userFollowStatus(token, profileId, logger) {
419
493
  if (error.message != 'Bad Request') {
420
494
  // if we hit a minor rate limit here, we wait for 5 seconds before attempting again, usually long enough to continue
421
495
  await new Promise(r => setTimeout(r, 5000));
422
- return userFollowStatus(token, profileId, logger);
496
+ return userFollowStatus(token, profileId, logger, authenticatedUser);
423
497
  }
424
498
  }
425
499
  }
@@ -478,7 +552,14 @@ export async function getBrandUserRelationship(token, profileId, brandProfileId,
478
552
  }
479
553
  };
480
554
  } catch (error) {
481
- loggerDebug(logger, `Error in getting user brand friendship info: ${profileId} and ${brandProfileId}`, error);
555
+ loggerInfo(logger, `Failed to call twitter api - getBrandUserRelationship`, {
556
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
557
+ profileId,
558
+ brandProfileId,
559
+ error: error?.message
560
+ })
561
+ });
562
+ throw error;
482
563
  }
483
564
  }
484
565
  export async function createTweet(token, text, attachment, logger) {
@@ -690,7 +771,6 @@ async function publishTweet(token, payload, query, isDirectMessage, logger) {
690
771
  );
691
772
  return normalizedResponse;
692
773
  } catch (err) {
693
- loggerError(logger, `Twitter publish exception details`, err);
694
774
  throw err;
695
775
  }
696
776
  }
@@ -723,7 +803,6 @@ async function publishDirectMessage(token, payload, query, logger) {
723
803
  const normalizedResponse = normalizeTwitterResponse(response, 'dm', logger);
724
804
  return normalizedResponse;
725
805
  } catch (err) {
726
- loggerError(logger, `Twitter DM send failed`, err);
727
806
  throw err;
728
807
  }
729
808
  }
@@ -844,10 +923,14 @@ async function doRequest(_ref5) {
844
923
  });
845
924
  return reject(e);
846
925
  }
847
- loggerError(logger, `Error in twitter doRequest`, e, {
848
- url,
849
- convertPayloadToUri,
850
- payload: JSON.stringify(payload)
926
+ loggerInfo(logger, `Failed to call twitter api - doRequest`, {
927
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
928
+ url,
929
+ convertPayloadToUri,
930
+ payload: JSON.stringify(payload),
931
+ status: e?.response?.status,
932
+ error: e?.response?.body?.errors?.[0]?.message ?? e?.message
933
+ })
851
934
  });
852
935
  reject(e);
853
936
  }
@@ -917,7 +1000,6 @@ async function uploadMedia(attachment, token, discussionType, logger) {
917
1000
  return mediaIdString;
918
1001
  }
919
1002
  } catch (e) {
920
- loggerError(logger, `Failed uploading media`, e);
921
1003
  if (filePath) {
922
1004
  removeMedia(filePath, logger);
923
1005
  }
@@ -952,7 +1034,13 @@ async function simpleMediaUpload(fileData, mimeType, mediaCategory, token, logge
952
1034
  });
953
1035
  return result.body.media_id_string;
954
1036
  } catch (e) {
955
- loggerError(logger, 'Error in simple media upload', e);
1037
+ loggerInfo(logger, `Failed to call twitter api - simpleMediaUpload`, {
1038
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
1039
+ mimeType,
1040
+ mediaCategory,
1041
+ error: e?.message
1042
+ })
1043
+ });
956
1044
  throw e;
957
1045
  }
958
1046
  }
@@ -1009,7 +1097,6 @@ async function chunkedMediaUpload(filePath, fileSize, mimeType, mediaCategory, t
1009
1097
  }
1010
1098
  return mediaId;
1011
1099
  } catch (e) {
1012
- loggerError(logger, 'Error in chunked media upload', e);
1013
1100
  throw e;
1014
1101
  }
1015
1102
  }
@@ -1060,7 +1147,12 @@ async function mediaUploadRequest(params, token, logger) {
1060
1147
  const result = await request;
1061
1148
  return result.body;
1062
1149
  } catch (e) {
1063
- loggerError(logger, `Media upload request failed for command: ${params.command}`, e);
1150
+ loggerInfo(logger, `Failed to call twitter api - mediaUploadRequest`, {
1151
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
1152
+ command: params.command,
1153
+ error: e?.message
1154
+ })
1155
+ });
1064
1156
  throw e;
1065
1157
  }
1066
1158
  }
@@ -1101,10 +1193,20 @@ function removeMedia(file, logger) {
1101
1193
  try {
1102
1194
  fs.unlink(file, function (err) {
1103
1195
  if (err) {
1104
- loggerError(logger, `Failed removing ${file}`, err);
1196
+ loggerInfo(logger, `Failed to call twitter api - removeMedia`, {
1197
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
1198
+ file,
1199
+ error: err?.message
1200
+ })
1201
+ });
1105
1202
  }
1106
1203
  });
1107
1204
  } catch (e) {
1108
- loggerError(logger, `failed trying to remove media ${file} it may have already been removed`);
1205
+ loggerInfo(logger, `Failed to call twitter api - removeMedia`, {
1206
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
1207
+ file,
1208
+ error: e?.message
1209
+ })
1210
+ });
1109
1211
  }
1110
1212
  }
@@ -1,6 +1,6 @@
1
1
  import superagent from 'superagent';
2
2
  import { removePrefix } from '../../lib/externalId.helpers.js';
3
- import { loggerDebug, loggerError, loggerInfo } from '../../lib/logger.helpers.js';
3
+ import { loggerInfo, MeltwaterAttributes } from '../../lib/logger.helpers.js';
4
4
  const YOUTUBE_API_URL = "https://www.googleapis.com/youtube/v3/";
5
5
  async function sendPost(token) {
6
6
  let paramString = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
@@ -10,11 +10,12 @@ async function sendPost(token) {
10
10
  try {
11
11
  response = await superagent.post(YOUTUBE_API_URL + paramString).set('Accept', 'application/json').set('Content-Type', 'application/json').set('Authorization', `Bearer ${token}`).send(postData);
12
12
  } catch (err) {
13
- if (err && err.response && err.response.body && err.response.body.error) {
14
- loggerError(logger, `Failed to call youtube api for paramString ${paramString}: ${err.response.body.error.message}`);
15
- } else {
16
- loggerError(logger, `Failed to call youtube api for paramString ${paramString}`, err);
17
- }
13
+ loggerInfo(logger, 'Failed to call youtube api - sendPost', {
14
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
15
+ paramString,
16
+ error: err?.response?.body?.error?.message ?? err?.message
17
+ })
18
+ });
18
19
  throw err;
19
20
  }
20
21
  return response;
@@ -26,18 +27,23 @@ export async function sendRequest(token) {
26
27
  try {
27
28
  response = await superagent.get(YOUTUBE_API_URL + paramString).set('Accept', 'application/json').set('Content-Type', 'application/json').set('Authorization', `Bearer ${token}`).send();
28
29
  } catch (err) {
29
- if (err && err.response && err.response.body && err.response.body.error) {
30
- loggerError(logger, `Failed to call youtube api for paramString ${paramString}: ${err.response.body.error.message}`);
31
- } else {
32
- loggerError(logger, `Failed to call youtube api for paramString ${paramString}`, err);
33
- }
30
+ loggerInfo(logger, 'Failed to call youtube api - sendRequest', {
31
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
32
+ paramString,
33
+ error: err?.response?.body?.error?.message ?? err?.message
34
+ })
35
+ });
34
36
  throw err;
35
37
  }
36
38
  if (response.status !== 200) {
37
- loggerError(logger, `Failed to call facebook api`, {
38
- responseBody: JSON.stringify(response.body)
39
+ loggerInfo(logger, 'Failed to call youtube api - sendRequest', {
40
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
41
+ paramString,
42
+ status: response.status,
43
+ body: response.body
44
+ })
39
45
  });
40
- let error = new Error(`Failed to call facebook api`);
46
+ let error = new Error(`Failed to call youtube api`);
41
47
  error.code = response.status;
42
48
  throw error;
43
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltwater/conversations-api-services",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Repository to contain all conversations api services shared across our services",
5
5
  "main": "dist/cjs/data-access/index.js",
6
6
  "module": "dist/esm/data-access/index.js",
@@ -39,8 +39,6 @@
39
39
  "@hapi/joi": "^17.1.1",
40
40
  "@meltwater/date-range": "^3.6.0",
41
41
  "@meltwater/engage-conversations-schemas": "^2.3.10",
42
- "@meltwater/xrunes": "^1.1.1",
43
- "@meltwater/xrunes-core": "^3.0.2",
44
42
  "apollo-server-hapi": "^3.13.0",
45
43
  "aws-sdk": "^2.1562.0",
46
44
  "dataloader": "^2.2.2",
@@ -0,0 +1,50 @@
1
+ # Native API HTTP Modules
2
+
3
+ Files matching `*.native.js` in this directory are thin HTTP clients for third-party social platform APIs (Instagram, Facebook, Twitter, LinkedIn, TikTok, YouTube, Threads). We do not control these APIs.
4
+
5
+ ## Logging contract
6
+
7
+ Because external API failures are expected and noisy, these modules follow a strict logging convention:
8
+
9
+ - **No `loggerError`** — use `loggerInfo` for all failures
10
+ - **`loggerWarn`** is allowed for known/expected failure cases
11
+ - **Every log must include a `PAYLOADDATA` block** — structured context that makes the log searchable and useful without being noisy
12
+
13
+ ### Pattern
14
+
15
+ ```js
16
+ loggerInfo(logger, 'Failed to call <platform> api', {
17
+ [MeltwaterAttributes.PAYLOADDATA]: JSON.stringify({
18
+ apiUrl, // endpoint called (no tokens in the URL)
19
+ someId, // relevant IDs
20
+ status, // HTTP status if available
21
+ error: err?.response?.body?.error ?? err?.message,
22
+ }),
23
+ });
24
+ ```
25
+
26
+ ### Rules for PAYLOADDATA contents
27
+
28
+ | Include | Exclude |
29
+ |---|---|
30
+ | API URL (path only, no credentials) | `accessToken`, `token`, any keys |
31
+ | IDs: `externalId`, `sourceId`, `inReplyToId`, etc. | Raw `err` objects |
32
+ | Operation params: `discussionType`, `hideStatus`, `fields`, etc. | Stack traces |
33
+ | HTTP `status` and `responseBody` on non-200 | |
34
+ | `error: err?.response?.body?.error ?? err?.message` | |
35
+
36
+ ## Reference implementation
37
+
38
+ See [instagram.native.js](./instagram.native.js) for a fully converted example.
39
+
40
+ ## Files
41
+
42
+ | File | Platform |
43
+ |---|---|
44
+ | `instagram.native.js` | Instagram Graph API |
45
+ | `facebook.native.js` | Facebook Graph API |
46
+ | `twitter.native.js` | Twitter/X API |
47
+ | `linkedin.native.js` | LinkedIn API |
48
+ | `tiktok.native.js` | TikTok API |
49
+ | `youtube.native.js` | YouTube Data API |
50
+ | `threads.native.js` | Threads API |