@foxtware/mineral 0.1.28 → 0.1.30

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 (95) hide show
  1. package/.creds.yml.sample +35 -2
  2. package/AGENTS.md +7 -0
  3. package/_build_scripts/createNewFunction.js +26 -3
  4. package/api/dpd/docs.md +6 -0
  5. package/api/dpd/dpd.constants.js +9 -0
  6. package/api/dpd/dpd.utils.js +145 -0
  7. package/api/dpd/dpdTrackingGet.js +64 -0
  8. package/api/dropbox/docs.md +7 -0
  9. package/api/dropbox/dropbox.constants.js +10 -0
  10. package/api/dropbox/dropbox.utils.js +67 -0
  11. package/api/dropbox/dropboxAccountGet.js +64 -0
  12. package/api/dropbox/dropboxFileCopy.js +67 -0
  13. package/api/dropbox/dropboxFileDelete.js +89 -0
  14. package/api/dropbox/dropboxFileDownload.js +139 -0
  15. package/api/dropbox/dropboxFileMetadataGet.js +100 -0
  16. package/api/dropbox/dropboxFileMove.js +67 -0
  17. package/api/dropbox/dropboxFileUpload.js +129 -0
  18. package/api/dropbox/dropboxFolderCreate.js +68 -0
  19. package/api/dropbox/dropboxFolderList.js +66 -0
  20. package/api/dropbox/dropboxGet.js +158 -0
  21. package/api/dropbox/dropboxSearch.js +161 -0
  22. package/api/dropbox/dropboxSharedLinkCreate.js +68 -0
  23. package/api/dropbox/dropboxSpaceUsageGet.js +55 -0
  24. package/api/dropbox/dropboxTemporaryLinkGet.js +75 -0
  25. package/api/fedex/docs.md +6 -0
  26. package/api/fedex/fedex.constants.js +11 -0
  27. package/api/fedex/fedex.utils.js +151 -0
  28. package/api/fedex/fedexTrackingGet.js +76 -0
  29. package/api/google/docs.md +1 -0
  30. package/api/google/google.constants.js +1 -0
  31. package/api/google/google.utils.js +21 -0
  32. package/api/google/googleanalyticsMetadataGet.js +62 -0
  33. package/api/google/googleanalyticsRealtimeReportRun.js +103 -0
  34. package/api/google/googleanalyticsReportRun.js +114 -0
  35. package/api/paypal/docs.md +14 -0
  36. package/api/paypal/paypal.constants.js +13 -0
  37. package/api/paypal/paypal.utils.js +166 -0
  38. package/api/paypal/paypalAccessTokenGet.js +81 -0
  39. package/api/paypal/paypalAuthorizationCapture.js +75 -0
  40. package/api/paypal/paypalAuthorizationGet.js +57 -0
  41. package/api/paypal/paypalBalancesGet.js +65 -0
  42. package/api/paypal/paypalCaptureGet.js +57 -0
  43. package/api/paypal/paypalCaptureRefund.js +78 -0
  44. package/api/paypal/paypalOrderAuthorize.js +69 -0
  45. package/api/paypal/paypalOrderCapture.js +69 -0
  46. package/api/paypal/paypalOrderCreate.js +81 -0
  47. package/api/paypal/paypalOrderGet.js +57 -0
  48. package/api/paypal/paypalRefundGet.js +57 -0
  49. package/api/paypal/paypalTransactionsGet.js +101 -0
  50. package/api/paypal/paypalUserInfoGet.js +62 -0
  51. package/api/royalmail/docs.md +6 -0
  52. package/api/royalmail/royalmail.constants.js +7 -0
  53. package/api/royalmail/royalmail.utils.js +55 -0
  54. package/api/royalmail/royalmailTrackingGet.js +70 -0
  55. package/api/shopify/shopify.utils.js +65 -4
  56. package/api/shopify/shopifyMetaobjectCreate.js +13 -6
  57. package/api/shopify/shopifyMetaobjectDelete.js +90 -0
  58. package/api/shopify/shopifyMetaobjectGet.js +80 -0
  59. package/api/shopify/shopifyMetaobjectUpdate.js +108 -0
  60. package/api/shopify/shopifyStorefrontSearch.js +158 -0
  61. package/api/spotify/docs.md +8 -0
  62. package/api/spotify/spotify.constants.js +12 -0
  63. package/api/spotify/spotify.utils.js +106 -0
  64. package/api/spotify/spotifyAlbumGet.js +76 -0
  65. package/api/spotify/spotifyAlbumTracksGet.js +49 -0
  66. package/api/spotify/spotifyArtistAlbumsGet.js +55 -0
  67. package/api/spotify/spotifyArtistGet.js +71 -0
  68. package/api/spotify/spotifyArtistTopTracksGet.js +56 -0
  69. package/api/spotify/spotifyGet.js +171 -0
  70. package/api/spotify/spotifyMeGet.js +48 -0
  71. package/api/spotify/spotifyPlayerCurrentlyPlayingGet.js +62 -0
  72. package/api/spotify/spotifyPlayerDevicesGet.js +51 -0
  73. package/api/spotify/spotifyPlayerGet.js +63 -0
  74. package/api/spotify/spotifyPlayerNext.js +55 -0
  75. package/api/spotify/spotifyPlayerPause.js +55 -0
  76. package/api/spotify/spotifyPlayerPlay.js +70 -0
  77. package/api/spotify/spotifyPlayerPrevious.js +55 -0
  78. package/api/spotify/spotifyPlaylistCreate.js +63 -0
  79. package/api/spotify/spotifyPlaylistGet.js +91 -0
  80. package/api/spotify/spotifyPlaylistItemsAdd.js +61 -0
  81. package/api/spotify/spotifyPlaylistItemsGet.js +58 -0
  82. package/api/spotify/spotifyPlaylistItemsRemove.js +67 -0
  83. package/api/spotify/spotifyPlaylistUpdate.js +64 -0
  84. package/api/spotify/spotifyPlaylistsGet.js +47 -0
  85. package/api/spotify/spotifySavedTracksGet.js +46 -0
  86. package/api/spotify/spotifySavedTracksRemove.js +59 -0
  87. package/api/spotify/spotifySavedTracksSave.js +59 -0
  88. package/api/spotify/spotifySearch.js +174 -0
  89. package/api/spotify/spotifyTrackGet.js +76 -0
  90. package/api/starshipit/starshipitProductUpdate.js +102 -0
  91. package/api/starshipit/starshipitProductsGet.js +2 -2
  92. package/api/stylearcade/stylearcadeGet.js +5 -2
  93. package/api/utils.js +39 -0
  94. package/package.json +1 -1
  95. package/server.js +3 -1
@@ -0,0 +1,158 @@
1
+ // https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
2
+
3
+ const { ArgsWarden, Getter } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { dropboxClient } = require('../dropbox/dropbox.utils');
6
+ const { MAX_PER_PAGE } = require('../dropbox/dropbox.constants');
7
+
8
+ const argsWarden = new ArgsWarden([
9
+ ['credsPayload', credsValidator],
10
+ ['url'],
11
+ ]);
12
+
13
+ const dropboxGetPacket = async (
14
+ credsPayload,
15
+ url,
16
+ {
17
+ body = {},
18
+ perPage = MAX_PER_PAGE,
19
+ fetchClient = dropboxClient,
20
+ } = {},
21
+ ) => {
22
+ // When continuing, Dropbox wants only { cursor } on list_folder/continue.
23
+ const isContinue = url.includes('/continue') || body.cursor;
24
+
25
+ const requestBody = isContinue
26
+ ? { cursor: body.cursor }
27
+ : {
28
+ ...body,
29
+ ...(body.limit === undefined && {
30
+ limit: Math.min(perPage, MAX_PER_PAGE),
31
+ }),
32
+ };
33
+
34
+ return fetchClient.fetch({
35
+ requestPayload: {
36
+ method: 'post',
37
+ url,
38
+ body: requestBody,
39
+ },
40
+ context: {
41
+ credsPayload,
42
+ },
43
+ });
44
+ };
45
+
46
+ const dropboxGetPaginator = async (currentParams, response) => {
47
+ if (!response?.ok) {
48
+ return [true];
49
+ }
50
+
51
+ const { has_more: hasMore, cursor } = response.data ?? {};
52
+ if (!hasMore || !cursor) {
53
+ return [true];
54
+ }
55
+
56
+ const { args, options } = currentParams;
57
+ const [, initialUrl] = args;
58
+
59
+ // Switch to the matching continue endpoint when still on the initial path.
60
+ let continueUrl = initialUrl;
61
+ if (!continueUrl.includes('/continue')) {
62
+ continueUrl = `${ initialUrl.replace(/\/$/, '') }/continue`;
63
+ }
64
+
65
+ return [false, {
66
+ args: [args[0], continueUrl],
67
+ options: {
68
+ ...options,
69
+ body: {
70
+ cursor,
71
+ },
72
+ },
73
+ }];
74
+ };
75
+
76
+ const dropboxGet = async (
77
+ returnGetter,
78
+
79
+ credsPayload,
80
+ url,
81
+ {
82
+ body,
83
+ perPage = MAX_PER_PAGE,
84
+ resultsKey = 'entries',
85
+ fetchClient,
86
+ ...getterOptions
87
+ } = {},
88
+ ) => {
89
+
90
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
91
+ credsPayload,
92
+ url,
93
+ });
94
+ if (rejectResponse) {
95
+ return rejectResponse;
96
+ }
97
+
98
+ const getter = new Getter(
99
+ {
100
+ args: [credsPayload, url],
101
+ options: {
102
+ body,
103
+ perPage,
104
+ fetchClient,
105
+ },
106
+ },
107
+ {
108
+ func: dropboxGetPacket,
109
+ digester: (response) => {
110
+ if (!response?.ok) {
111
+ return [];
112
+ }
113
+
114
+ if (resultsKey) {
115
+ return response.data?.[resultsKey] ?? [];
116
+ }
117
+
118
+ return response.data ?? [];
119
+ },
120
+ paginator: dropboxGetPaginator,
121
+ ...getterOptions,
122
+ },
123
+ );
124
+
125
+ if (returnGetter) {
126
+ return getter;
127
+ }
128
+
129
+ const data = await getter.run({ returnAll: true });
130
+
131
+ return {
132
+ ok: true,
133
+ data,
134
+ };
135
+ };
136
+
137
+ const funcApiConfig = {
138
+ argsWarden,
139
+ };
140
+
141
+ module.exports = {
142
+ dropboxGet: (...args) => dropboxGet(false, ...args),
143
+ dropboxGetter: (...args) => dropboxGet(true, ...args),
144
+ funcApiConfig,
145
+ };
146
+
147
+ /*
148
+ curl -X POST "http://localhost:8000/dropboxGet" \
149
+ -H "Content-Type: application/json" \
150
+ -d '{
151
+ "credsPayload": { "credsPath": "dropbox" },
152
+ "url": "/files/list_folder",
153
+ "options": {
154
+ "body": { "path": "" },
155
+ "perPage": 100
156
+ }
157
+ }'
158
+ */
@@ -0,0 +1,161 @@
1
+ // https://www.dropbox.com/developers/documentation/http/documentation#files-search_v2
2
+
3
+ const { ArgsWarden, Getter, logDeep } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { dropboxClient } = require('../dropbox/dropbox.utils');
6
+ const { MAX_PER_PAGE } = require('../dropbox/dropbox.constants');
7
+
8
+ const argsWarden = new ArgsWarden([
9
+ ['credsPayload', credsValidator],
10
+ ['query'],
11
+ ]);
12
+
13
+ const dropboxSearchPacket = async (
14
+ credsPayload,
15
+ query,
16
+ {
17
+ body = {},
18
+ perPage = 100,
19
+ fetchClient = dropboxClient,
20
+ } = {},
21
+ ) => {
22
+ if (body.cursor) {
23
+ return fetchClient.fetch({
24
+ context: { credsPayload },
25
+ requestPayload: {
26
+ method: 'post',
27
+ url: '/files/search/continue_v2',
28
+ body: { cursor: body.cursor },
29
+ },
30
+ });
31
+ }
32
+
33
+ return fetchClient.fetch({
34
+ context: { credsPayload },
35
+ requestPayload: {
36
+ method: 'post',
37
+ url: '/files/search_v2',
38
+ body: {
39
+ query,
40
+ options: {
41
+ max_results: Math.min(perPage, MAX_PER_PAGE),
42
+ ...(body.options || {}),
43
+ },
44
+ ...(body.match_field_options && {
45
+ match_field_options: body.match_field_options,
46
+ }),
47
+ },
48
+ },
49
+ });
50
+ };
51
+
52
+ const dropboxSearchPaginator = async (currentParams, response) => {
53
+ if (!response?.ok) {
54
+ return [true];
55
+ }
56
+
57
+ const { has_more: hasMore, cursor } = response.data ?? {};
58
+ if (!hasMore || !cursor) {
59
+ return [true];
60
+ }
61
+
62
+ const { args, options } = currentParams;
63
+
64
+ return [false, {
65
+ args,
66
+ options: {
67
+ ...options,
68
+ body: {
69
+ cursor,
70
+ },
71
+ },
72
+ }];
73
+ };
74
+
75
+ const dropboxSearch = async (
76
+ returnGetter,
77
+
78
+ credsPayload,
79
+ query,
80
+ {
81
+ path,
82
+ fileStatus,
83
+ filenameOnly,
84
+ maxResults,
85
+ fileCategories,
86
+ accountId,
87
+ perPage = 100,
88
+ fetchClient,
89
+ ...getterOptions
90
+ } = {},
91
+ ) => {
92
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
93
+ credsPayload,
94
+ query,
95
+ });
96
+ if (rejectResponse) {
97
+ return rejectResponse;
98
+ }
99
+
100
+ const options = {
101
+ ...(path !== undefined && { path }),
102
+ ...(fileStatus !== undefined && { file_status: fileStatus }),
103
+ ...(filenameOnly !== undefined && { filename_only: filenameOnly }),
104
+ ...(maxResults !== undefined && { max_results: maxResults }),
105
+ ...(fileCategories !== undefined && { file_categories: fileCategories }),
106
+ ...(accountId !== undefined && { account_id: accountId }),
107
+ };
108
+
109
+ const getter = new Getter(
110
+ {
111
+ args: [credsPayload, query],
112
+ options: {
113
+ body: { options },
114
+ perPage: maxResults ?? perPage,
115
+ fetchClient,
116
+ },
117
+ },
118
+ {
119
+ func: dropboxSearchPacket,
120
+ digester: (response) => {
121
+ if (!response?.ok) {
122
+ return [];
123
+ }
124
+ return response.data?.matches ?? [];
125
+ },
126
+ paginator: dropboxSearchPaginator,
127
+ ...getterOptions,
128
+ },
129
+ );
130
+
131
+ if (returnGetter) {
132
+ return getter;
133
+ }
134
+
135
+ const data = await getter.run({ returnAll: true });
136
+
137
+ return {
138
+ ok: true,
139
+ data,
140
+ };
141
+ };
142
+
143
+ const funcApiConfig = {
144
+ argsWarden,
145
+ };
146
+
147
+ module.exports = {
148
+ dropboxSearch: (...args) => dropboxSearch(false, ...args),
149
+ dropboxSearchGetter: (...args) => dropboxSearch(true, ...args),
150
+ funcApiConfig,
151
+ };
152
+
153
+ /*
154
+ curl -X POST "http://localhost:8000/dropboxSearch" \
155
+ -H "Content-Type: application/json" \
156
+ -d '{
157
+ "credsPayload": { "credsPath": "dropbox" },
158
+ "query": "prime",
159
+ "options": { "path": "/Homework" }
160
+ }'
161
+ */
@@ -0,0 +1,68 @@
1
+ // https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings
2
+
3
+ const { ArgsWarden, logDeep } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { dropboxClient } = require('../dropbox/dropbox.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ['path'],
10
+ ]);
11
+
12
+ const dropboxSharedLinkCreate = async (
13
+ credsPayload,
14
+ path,
15
+ {
16
+ settings,
17
+ fetchClient = dropboxClient,
18
+ } = {},
19
+ ) => {
20
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
21
+ credsPayload,
22
+ path,
23
+ });
24
+ if (rejectResponse) {
25
+ return rejectResponse;
26
+ }
27
+
28
+ const response = await fetchClient.fetch({
29
+ context: { credsPayload },
30
+ requestPayload: {
31
+ method: 'post',
32
+ url: '/sharing/create_shared_link_with_settings',
33
+ body: {
34
+ path,
35
+ ...(settings !== undefined && { settings }),
36
+ },
37
+ },
38
+ });
39
+
40
+ const { ok, data, error } = response;
41
+ if (!ok) {
42
+ logDeep({ error });
43
+ return { ok: false, error };
44
+ }
45
+
46
+ return {
47
+ ok: true,
48
+ data,
49
+ };
50
+ };
51
+
52
+ const funcApiConfig = {
53
+ argsWarden,
54
+ };
55
+
56
+ module.exports = {
57
+ dropboxSharedLinkCreate,
58
+ funcApiConfig,
59
+ };
60
+
61
+ /*
62
+ curl -X POST "http://localhost:8000/dropboxSharedLinkCreate" \
63
+ -H "Content-Type: application/json" \
64
+ -d '{
65
+ "credsPayload": { "credsPath": "dropbox" },
66
+ "path": "/Homework/math/Prime_Numbers.txt"
67
+ }'
68
+ */
@@ -0,0 +1,55 @@
1
+ // https://www.dropbox.com/developers/documentation/http/documentation#users-get_space_usage
2
+
3
+ const { ArgsWarden, logDeep } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { dropboxClient } = require('../dropbox/dropbox.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ]);
10
+
11
+ const dropboxSpaceUsageGet = async (
12
+ credsPayload,
13
+ {
14
+ fetchClient = dropboxClient,
15
+ } = {},
16
+ ) => {
17
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
18
+ credsPayload,
19
+ });
20
+ if (rejectResponse) {
21
+ return rejectResponse;
22
+ }
23
+
24
+ const response = await fetchClient.fetch({
25
+ context: { credsPayload },
26
+ requestPayload: {
27
+ method: 'post',
28
+ url: '/users/get_space_usage',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ },
32
+ body: 'null',
33
+ },
34
+ });
35
+
36
+ const { ok, data, error } = response;
37
+ if (!ok) {
38
+ logDeep({ error });
39
+ return { ok: false, error };
40
+ }
41
+
42
+ return {
43
+ ok: true,
44
+ data,
45
+ };
46
+ };
47
+
48
+ const funcApiConfig = {
49
+ argsWarden,
50
+ };
51
+
52
+ module.exports = {
53
+ dropboxSpaceUsageGet,
54
+ funcApiConfig,
55
+ };
@@ -0,0 +1,75 @@
1
+ // https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
2
+
3
+ const { ArgsWarden, actionSingleOrMultiple, logDeep } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { dropboxClient } = require('../dropbox/dropbox.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ['path'],
10
+ ]);
11
+
12
+ const dropboxTemporaryLinkGetSingle = async (
13
+ credsPayload,
14
+ path,
15
+ {
16
+ fetchClient = dropboxClient,
17
+ } = {},
18
+ ) => {
19
+ const response = await fetchClient.fetch({
20
+ context: { credsPayload },
21
+ requestPayload: {
22
+ method: 'post',
23
+ url: '/files/get_temporary_link',
24
+ body: { path },
25
+ },
26
+ });
27
+
28
+ const { ok, data, error } = response;
29
+ if (!ok) {
30
+ logDeep({ error });
31
+ return { ok: false, error };
32
+ }
33
+
34
+ return {
35
+ ok: true,
36
+ data,
37
+ };
38
+ };
39
+
40
+ const dropboxTemporaryLinkGet = async (
41
+ credsPayload,
42
+ path,
43
+ {
44
+ queueRunOptions,
45
+ fetchClient,
46
+ } = {},
47
+ ) => {
48
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
49
+ credsPayload,
50
+ path,
51
+ });
52
+ if (rejectResponse) {
53
+ return rejectResponse;
54
+ }
55
+
56
+ return actionSingleOrMultiple(
57
+ path,
58
+ dropboxTemporaryLinkGetSingle,
59
+ (pathItem) => ({
60
+ args: [credsPayload, pathItem, { fetchClient }],
61
+ }),
62
+ {
63
+ ...(queueRunOptions ? { queueRunOptions } : {}),
64
+ },
65
+ );
66
+ };
67
+
68
+ const funcApiConfig = {
69
+ argsWarden,
70
+ };
71
+
72
+ module.exports = {
73
+ dropboxTemporaryLinkGet,
74
+ funcApiConfig,
75
+ };
@@ -0,0 +1,6 @@
1
+ # FedEx
2
+
3
+ - [FedEx Developer Portal](https://developer.fedex.com/)
4
+ - [FedEx Track API](https://developer.fedex.com/api/en-us/catalog/track.html)
5
+
6
+ FedEx tracking uses OAuth 2.0 client credentials. Create a project with the Track API enabled, obtain a bearer token from `https://apis.fedex.com/oauth/token`, and submit tracking requests to `https://apis.fedex.com/track/v1/trackingnumbers`. Sandbox requests use `https://apis-sandbox.fedex.com`.
@@ -0,0 +1,11 @@
1
+ const FEDEX_LIVE_BASE_URL = 'https://apis.fedex.com';
2
+ const FEDEX_SANDBOX_BASE_URL = 'https://apis-sandbox.fedex.com';
3
+ const FEDEX_OAUTH_PATH = '/oauth/token';
4
+ const FEDEX_TRACKING_PATH = '/track/v1/trackingnumbers';
5
+
6
+ module.exports = {
7
+ FEDEX_LIVE_BASE_URL,
8
+ FEDEX_SANDBOX_BASE_URL,
9
+ FEDEX_OAUTH_PATH,
10
+ FEDEX_TRACKING_PATH,
11
+ };
@@ -0,0 +1,151 @@
1
+ const { resolveCreds } = require('../pipelineSteps');
2
+ const {
3
+ FetchClient,
4
+ appendUrlToBase,
5
+ customFetch,
6
+ fetchClientCommonSteps,
7
+ } = require('../utils');
8
+ const {
9
+ FEDEX_LIVE_BASE_URL,
10
+ FEDEX_OAUTH_PATH,
11
+ FEDEX_SANDBOX_BASE_URL,
12
+ } = require('./fedex.constants');
13
+
14
+ const accessTokenCache = new Map();
15
+
16
+ const isSandboxCreds = (creds) => {
17
+ return creds?.SANDBOX === true
18
+ || creds?.SANDBOX === 'true'
19
+ || creds?.ENVIRONMENT === 'sandbox';
20
+ };
21
+
22
+ const baseUrlForCreds = (creds) => {
23
+ return (creds?.BASE_URL || (
24
+ isSandboxCreds(creds)
25
+ ? FEDEX_SANDBOX_BASE_URL
26
+ : FEDEX_LIVE_BASE_URL
27
+ )).replace(/\/$/, '');
28
+ };
29
+
30
+ const getAccessToken = async (creds) => {
31
+ if (creds?.ACCESS_TOKEN) {
32
+ return creds.ACCESS_TOKEN;
33
+ }
34
+
35
+ const {
36
+ CLIENT_ID,
37
+ CLIENT_SECRET,
38
+ } = creds || {};
39
+ if (!CLIENT_ID || !CLIENT_SECRET) {
40
+ throw new Error(
41
+ 'FedEx creds require ACCESS_TOKEN or CLIENT_ID and CLIENT_SECRET',
42
+ );
43
+ }
44
+
45
+ const cacheKey = `${ baseUrlForCreds(creds) }:${ CLIENT_ID }`;
46
+ const cachedToken = accessTokenCache.get(cacheKey);
47
+ if (cachedToken && cachedToken.expiresAt > Date.now()) {
48
+ return cachedToken.accessToken;
49
+ }
50
+
51
+ const basicAuth = Buffer
52
+ .from(`${ CLIENT_ID }:${ CLIENT_SECRET }`)
53
+ .toString('base64');
54
+ const response = await customFetch(
55
+ appendUrlToBase(baseUrlForCreds(creds), FEDEX_OAUTH_PATH),
56
+ {
57
+ method: 'post',
58
+ headers: {
59
+ Accept: 'application/json',
60
+ Authorization: `Basic ${ basicAuth }`,
61
+ 'Content-Type': 'application/x-www-form-urlencoded',
62
+ },
63
+ body: 'grant_type=client_credentials',
64
+ },
65
+ );
66
+
67
+ if (!response.ok) {
68
+ throw new Error(
69
+ `FedEx authentication failed: ${ JSON.stringify(response.error || response.data) }`,
70
+ );
71
+ }
72
+
73
+ const {
74
+ access_token: accessToken,
75
+ expires_in: expiresIn = 3600,
76
+ } = response.data || {};
77
+ if (!accessToken) {
78
+ throw new Error('FedEx authentication response missing access_token');
79
+ }
80
+
81
+ accessTokenCache.set(cacheKey, {
82
+ accessToken,
83
+ expiresAt: Date.now() + (Math.max(expiresIn - 60, 60) * 1000),
84
+ });
85
+
86
+ return accessToken;
87
+ };
88
+
89
+ const useBaseUrlFromCreds = async (state) => {
90
+ const { requestPayload, context } = state;
91
+ const { creds } = context;
92
+
93
+ return {
94
+ requestPayload: {
95
+ ...requestPayload,
96
+ url: appendUrlToBase(
97
+ baseUrlForCreds(creds),
98
+ requestPayload.url || '',
99
+ ),
100
+ },
101
+ };
102
+ };
103
+
104
+ const useAuthHeaders = async (state) => {
105
+ const { requestPayload, context } = state;
106
+ const { creds } = context;
107
+
108
+ let accessToken;
109
+ try {
110
+ accessToken = await getAccessToken(creds);
111
+ } catch (error) {
112
+ return {
113
+ breakChain: true,
114
+ response: {
115
+ ok: false,
116
+ error: {
117
+ code: 'FEDEX_AUTH_ERROR',
118
+ message: error.message,
119
+ },
120
+ },
121
+ };
122
+ }
123
+
124
+ return {
125
+ requestPayload: {
126
+ ...requestPayload,
127
+ headers: {
128
+ Accept: 'application/json',
129
+ Authorization: `Bearer ${ accessToken }`,
130
+ ...requestPayload.headers,
131
+ },
132
+ },
133
+ };
134
+ };
135
+
136
+ const fedexClient = new FetchClient({
137
+ pipeline: [
138
+ resolveCreds,
139
+ useBaseUrlFromCreds,
140
+ useAuthHeaders,
141
+ 'fetch',
142
+ fetchClientCommonSteps.exitEarlyOnNotOk,
143
+ ],
144
+ });
145
+
146
+ module.exports = {
147
+ accessTokenCache,
148
+ baseUrlForCreds,
149
+ fedexClient,
150
+ getAccessToken,
151
+ };