@mcpher/gas-fakes 1.2.32 → 2.0.1
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.
- package/.gcloudignore +24 -0
- package/README.md +5 -5
- package/appsscript.json +101 -0
- package/exgcp.sh +4 -4
- package/package.json +5 -6
- package/src/cli/app.js +9 -1
- package/src/cli/lib-manager.js +0 -1
- package/src/cli/setup.js +330 -228
- package/src/services/advcalendar/clapis.js +4 -2
- package/src/services/advdocs/docapis.js +4 -3
- package/src/services/advdrive/drapis.js +8 -5
- package/src/services/advforms/formsapis.js +4 -3
- package/src/services/advgmail/gmailapis.js +4 -3
- package/src/services/advsheets/shapis.js +5 -10
- package/src/services/advslides/slapis.js +4 -3
- package/src/services/driveapp/fakedriveapp.js +10 -2
- package/src/services/logger/fakelogger.js +6 -3
- package/src/services/scriptapp/app.js +16 -11
- package/src/services/scriptapp/behavior.js +132 -107
- package/src/services/session/fakesession.js +24 -9
- package/src/services/slidesapp/fakepresentation.js +19 -8
- package/src/services/slidesapp/fakeslide.js +45 -20
- package/src/services/spreadsheetapp/fakesheet.js +9 -7
- package/src/services/stores/fakestores.js +20 -18
- package/src/services/stores/gasflex.js +13 -14
- package/src/services/urlfetchapp/app.js +3 -3
- package/src/support/auth.js +227 -55
- package/src/support/slogger.js +42 -0
- package/src/support/sxauth.js +42 -39
- package/src/support/sxcalendar.js +9 -43
- package/src/support/sxdocs.js +6 -42
- package/src/support/sxdrive.js +19 -76
- package/src/support/sxfetch.js +36 -30
- package/src/support/sxforms.js +9 -40
- package/src/support/sxgmail.js +9 -37
- package/src/support/sxretry.js +79 -0
- package/src/support/sxsheets.js +6 -37
- package/src/support/sxslides.js +5 -36
- package/src/support/sxtoken.js +15 -0
- package/src/support/syncit.js +27 -10
- package/src/support/workersync/sxfunctions.js +2 -0
- package/src/support/workersync/synclogger.js +22 -5
- package/src/support/workersync/worker.js +8 -11
- package/README.RU.md +0 -373
- package/env.setup.template +0 -16
- package/run.js +0 -35
- package/setup.js +0 -689
- package/src/Code.js +0 -3
- package/src/appsscript.json +0 -5
package/src/support/sxdrive.js
CHANGED
|
@@ -7,92 +7,35 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { responseSyncify } from './auth.js';
|
|
10
|
+
import { sxRetry } from './sxretry.js';
|
|
10
11
|
import intoStream from 'into-stream';
|
|
11
12
|
import { getStreamAsBuffer } from 'get-stream';
|
|
12
13
|
import { syncWarn, syncError, syncLog } from './workersync/synclogger.js';
|
|
13
14
|
import { getDriveApiClient } from '../services/advdrive/drapis.js';
|
|
14
15
|
import { translateFieldsToV2 } from './utils.js';
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* serializable reponse from a sync call
|
|
19
|
-
* @typedef SxResponse
|
|
20
|
-
* @property {number} status
|
|
21
|
-
* @property {string} statusText
|
|
22
|
-
* @property {string} responseUrl
|
|
23
|
-
* @property {boolean} fromCache
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* what's eventually returned from the sync call
|
|
28
|
-
* @typedef SxResult
|
|
29
|
-
* @property {*} data the data from the api call
|
|
30
|
-
* @property {SxResponse} reponse the response from the api call
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* serialized blob
|
|
35
|
-
* @typedef SerializedBlob
|
|
36
|
-
* @property {string} name
|
|
37
|
-
* @property {byte[]} bytes
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
42
|
-
|
|
43
17
|
export const sxDrive = async (Auth, { prop, method, params, options }) => {
|
|
44
18
|
|
|
45
19
|
const apiClient = getDriveApiClient();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
20
|
+
const tag = `sxDrive for ${prop}.${method}`;
|
|
21
|
+
|
|
22
|
+
return sxRetry(Auth, tag, async () => {
|
|
23
|
+
return apiClient[prop][method](params, options);
|
|
24
|
+
}, {
|
|
25
|
+
extraRetryCheck: (error, response) => {
|
|
26
|
+
// handle invalid field selection - sometimes old files dont support createdTime or modifiedTime
|
|
27
|
+
// we'll try to fallback to createdDate and modifiedDate
|
|
28
|
+
const isInvalidField = error?.message?.includes("Invalid field selection") && (params?.fields?.includes("createdTime") || params?.fields?.includes("modifiedTime"));
|
|
29
|
+
|
|
30
|
+
if (isInvalidField) {
|
|
31
|
+
const fileId = params?.fileId ? ` for file ${params.fileId}` : "";
|
|
32
|
+
syncWarn(`Invalid field selection error on Drive API call ${prop}.${method}${fileId}. Retrying with v2 field names...`);
|
|
33
|
+
params.fields = translateFieldsToV2(params.fields);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
60
37
|
}
|
|
61
|
-
|
|
62
|
-
const isRetryable = [429, 500, 503].includes(response?.status) || error?.code == 429;
|
|
63
|
-
|
|
64
|
-
// handle invalid field selection - sometimes old files dont support createdTime or modifiedTime
|
|
65
|
-
// we'll try to fallback to createdDate and modifiedDate
|
|
66
|
-
const isInvalidField = error?.message?.includes("Invalid field selection") && (params?.fields?.includes("createdTime") || params?.fields?.includes("modifiedTime"));
|
|
67
|
-
|
|
68
|
-
if (isInvalidField && i < maxRetries - 1) {
|
|
69
|
-
const fileId = params?.fileId ? ` for file ${params.fileId}` : "";
|
|
70
|
-
syncWarn(`Invalid field selection error on Drive API call ${prop}.${method}${fileId}. Retrying with v2 field names...`);
|
|
71
|
-
params.fields = translateFieldsToV2(params.fields);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (isRetryable && i < maxRetries - 1) {
|
|
76
|
-
// add a random jitter to avoid thundering herd
|
|
77
|
-
const jitter = Math.floor(Math.random() * 1000);
|
|
78
|
-
syncWarn(`Retryable error on Drive API call ${prop}.${method} (status: ${response?.status}). Retrying in ${delay + jitter}ms...`);
|
|
79
|
-
await sleep(delay + jitter);
|
|
80
|
-
delay *= 2;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (error || isRetryable) {
|
|
85
|
-
syncError(`Failed in sxDrive for ${prop}.${method}`, error);
|
|
86
|
-
return {
|
|
87
|
-
data: null,
|
|
88
|
-
response: responseSyncify(response)
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
data: response.data,
|
|
93
|
-
response: responseSyncify(response)
|
|
94
|
-
};
|
|
95
|
-
}
|
|
38
|
+
});
|
|
96
39
|
};
|
|
97
40
|
|
|
98
41
|
/**
|
package/src/support/sxfetch.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* all these functions run in the worker
|
|
3
|
-
* thus turning async operations into sync
|
|
4
|
-
* note
|
|
5
|
-
* - arguments and returns must be serializable ie. primitives or plain objects
|
|
6
|
-
*/
|
|
1
|
+
import { sxRetry } from './sxretry.js';
|
|
7
2
|
import got from 'got';
|
|
8
3
|
|
|
9
4
|
|
|
@@ -42,34 +37,45 @@ const fixOptions = (options) => {
|
|
|
42
37
|
* @returns {object} an http type response
|
|
43
38
|
*/
|
|
44
39
|
export const sxFetch = async (Auth, url, options, responseFields) => {
|
|
45
|
-
// we need special headers if we're calling google apis
|
|
46
|
-
options = Auth.googify(options)
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
// The caller (UrlFetchApp/HttpResponse) will be responsible for decoding to text if needed.
|
|
50
|
-
const fixedOptions = fixOptions(options)
|
|
41
|
+
const tag = `sxFetch for ${url}`;
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
return sxRetry(Auth, tag, async () => {
|
|
44
|
+
// we need special headers if we're calling google apis
|
|
45
|
+
// notably, the token might be refreshed on retry, so googify must be inside
|
|
46
|
+
const googOptions = Auth.googify(options)
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
p[c] = response[c]
|
|
61
|
-
return p
|
|
62
|
-
}, {})
|
|
48
|
+
// Always fetch as a buffer to prevent corruption of binary data like images.
|
|
49
|
+
// The caller (UrlFetchApp/HttpResponse) will be responsible for decoding to text if needed.
|
|
50
|
+
const fixedOptions = fixOptions(googOptions)
|
|
63
51
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
const response = await got(url, {
|
|
53
|
+
...fixedOptions,
|
|
54
|
+
responseType: 'buffer'
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// we cant return the response from this as it cant be serialized
|
|
58
|
+
// so we;ll extract oout the fields required
|
|
59
|
+
const result = responseFields.reduce((p, c) => {
|
|
60
|
+
p[c] = response[c]
|
|
61
|
+
return p
|
|
62
|
+
}, {})
|
|
63
|
+
|
|
64
|
+
// The rawBody is a Buffer. Convert it to a byte array for proper serialization across the worker boundary.
|
|
65
|
+
if (result.rawBody) {
|
|
66
|
+
result.rawBody = Array.from(result.rawBody);
|
|
67
|
+
}
|
|
68
|
+
// The body will also be a buffer
|
|
69
|
+
if (result.body && Buffer.isBuffer(result.body)) {
|
|
70
|
+
result.body = Array.from(result.body);
|
|
71
|
+
}
|
|
72
|
+
// we return what sxRetry expects: an object with data and response (or just the object if it's the result)
|
|
73
|
+
// Actually sxRetry expects the result of the func to have .data and .response or it will use the result itself.
|
|
74
|
+
// sxFetch returns the result fields directly in the GAS implementation.
|
|
75
|
+
// Let's wrap it for sxRetry
|
|
76
|
+
return { data: result, response };
|
|
77
|
+
|
|
78
|
+
});
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
/**
|
package/src/support/sxforms.js
CHANGED
|
@@ -6,51 +6,20 @@
|
|
|
6
6
|
* - arguments and returns must be serializable ie. primitives or plain objects
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { syncWarn, syncError, syncLog } from './workersync/synclogger.js';
|
|
9
|
+
import { sxRetry } from './sxretry.js';
|
|
11
10
|
import { getFormsApiClient } from '../services/advforms/formsapis.js';
|
|
12
11
|
|
|
13
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
-
|
|
15
12
|
export const sxForms = async (Auth, { prop, subProp, method, params, options = {} }) => {
|
|
16
13
|
|
|
17
14
|
const apiClient = getFormsApiClient();
|
|
15
|
+
const tag = `sxForms for ${prop}${subProp ? '.' + subProp : ''}.${method}`;
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let error;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
// Access the base property (e.g., 'forms'), then the sub-property if it exists (e.g., 'responses').
|
|
28
|
-
let callish = apiClient[prop];
|
|
29
|
-
if (subProp) {
|
|
30
|
-
callish = callish[subProp];
|
|
31
|
-
}
|
|
32
|
-
response = await callish[method](params, options);
|
|
33
|
-
//syncLog(`Forms API Response: ${prop}${subProp ? '.' + subProp : ''}.${method}, Status: ${response?.status}, Data: ${JSON.stringify(response?.data)}`);
|
|
34
|
-
} catch (err) {
|
|
35
|
-
error = err;
|
|
36
|
-
response = err.response;
|
|
37
|
-
syncError(`Forms API Error Response: ${prop}${subProp ? '.' + subProp : ''}.${method}, Status: ${response?.status}, Error: ${JSON.stringify(error)}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const isRetryable = [429, 500, 503].includes(response?.status) || error?.code == 429;
|
|
41
|
-
|
|
42
|
-
if (isRetryable && i < maxRetries - 1) {
|
|
43
|
-
const jitter = Math.floor(Math.random() * 1000);
|
|
44
|
-
syncWarn(`Retryable error on Forms API call ${prop}${subProp ? '.' + subProp : ''}.${method} (status: ${response?.status}). Retrying in ${delay + jitter}ms...`);
|
|
45
|
-
await sleep(delay + jitter);
|
|
46
|
-
delay *= 2;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (error || isRetryable) {
|
|
51
|
-
syncError(`Failed in sxForms for ${prop}${subProp ? '.' + subProp : ''}.${method}`, error);
|
|
52
|
-
return { data: null, response: responseSyncify(response) };
|
|
17
|
+
return sxRetry(Auth, tag, async () => {
|
|
18
|
+
// Access the base property (e.g., 'forms'), then the sub-property if it exists (e.g., 'responses').
|
|
19
|
+
let callish = apiClient[prop];
|
|
20
|
+
if (subProp) {
|
|
21
|
+
callish = callish[subProp];
|
|
53
22
|
}
|
|
54
|
-
return
|
|
55
|
-
}
|
|
23
|
+
return callish[method](params, options);
|
|
24
|
+
});
|
|
56
25
|
};
|
package/src/support/sxgmail.js
CHANGED
|
@@ -6,51 +6,23 @@
|
|
|
6
6
|
* - arguments and returns must be serializable ie. primitives or plain objects
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { syncWarn, syncError } from './workersync/synclogger.js';
|
|
9
|
+
import { sxRetry } from './sxretry.js';
|
|
11
10
|
import { getGmailApiClient } from '../services/advgmail/gmailapis.js';
|
|
12
11
|
|
|
13
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
-
|
|
15
12
|
export const sxGmail = async (Auth, { subProp, prop, method, params, options }) => {
|
|
16
13
|
|
|
17
14
|
const apiClient = getGmailApiClient();
|
|
18
|
-
const maxRetries = 7;
|
|
19
|
-
let delay = 1777;
|
|
20
15
|
const methodName = subProp ? `${prop}.${subProp}.${method}` : `${prop}.${method}`;
|
|
16
|
+
const tag = `sxGmail for ${methodName}`;
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
response = await callish[method](params, options);
|
|
28
|
-
} catch (err) {
|
|
29
|
-
error = err;
|
|
30
|
-
response = err.response;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const isRetryable = [429, 500, 503].includes(response?.status) || error?.code == 429;
|
|
34
|
-
|
|
35
|
-
if (isRetryable && i < maxRetries - 1) {
|
|
36
|
-
const jitter = Math.floor(Math.random() * 1000);
|
|
37
|
-
syncWarn(`Retryable error on Gmail API call ${methodName} (status: ${response?.status}). Retrying in ${delay + jitter}ms...`);
|
|
38
|
-
await sleep(delay + jitter);
|
|
39
|
-
delay *= 2;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (error || isRetryable) {
|
|
18
|
+
return sxRetry(Auth, tag, async () => {
|
|
19
|
+
const callish = subProp ? apiClient[prop][subProp] : apiClient[prop];
|
|
20
|
+
return callish[method](params, options);
|
|
21
|
+
}, {
|
|
22
|
+
skipLog: (error, response) => {
|
|
44
23
|
// Don't log 404, 409, or 400 as an error.
|
|
45
24
|
// 400 (Invalid delete request) happens when we try to delete an ID as a label but it's not a label.
|
|
46
|
-
|
|
47
|
-
syncError(`Failed in sxGmail for ${methodName}`, error);
|
|
48
|
-
}
|
|
49
|
-
return { data: null, response: responseSyncify(response) };
|
|
25
|
+
return response?.status === 404 || response?.status === 409 || response?.status === 400;
|
|
50
26
|
}
|
|
51
|
-
|
|
52
|
-
data: response.data,
|
|
53
|
-
response: responseSyncify(response)
|
|
54
|
-
};
|
|
55
|
-
}
|
|
27
|
+
});
|
|
56
28
|
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { responseSyncify } from './auth.js';
|
|
2
|
+
import { syncWarn, syncError } from './workersync/synclogger.js';
|
|
3
|
+
|
|
4
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared retry logic for worker-based API calls
|
|
8
|
+
* @param {object} Auth the Auth object
|
|
9
|
+
* @param {string} tag for logging
|
|
10
|
+
* @param {function} func the function to execute
|
|
11
|
+
* @param {object} [options] options
|
|
12
|
+
* @param {number} [options.maxRetries=7] max retries
|
|
13
|
+
* @param {number} [options.initialDelay=1777] initial delay
|
|
14
|
+
* @param {function} [options.extraRetryCheck] extra check for retryability
|
|
15
|
+
* @param {function} [options.skipLog] check to skip syncError logging
|
|
16
|
+
* @returns {object} { data, response }
|
|
17
|
+
*/
|
|
18
|
+
export const sxRetry = async (Auth, tag, func, options = {}) => {
|
|
19
|
+
const {
|
|
20
|
+
maxRetries = 7,
|
|
21
|
+
initialDelay = 1777,
|
|
22
|
+
extraRetryCheck = () => false,
|
|
23
|
+
skipLog = () => false
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
let delay = initialDelay;
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
29
|
+
let response;
|
|
30
|
+
let error;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
response = await func();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
error = err;
|
|
36
|
+
response = err.response;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const redoCodes = [429, 500, 503, 408, 401]
|
|
40
|
+
const isRetryable = redoCodes.includes(error?.code) ||
|
|
41
|
+
redoCodes.includes(response?.status) ||
|
|
42
|
+
error?.code === 'ETIMEDOUT' ||
|
|
43
|
+
error?.code === 'ECONNRESET' ||
|
|
44
|
+
error?.cause?.code === 'ETIMEDOUT' ||
|
|
45
|
+
error?.cause?.code === 'ECONNRESET' ||
|
|
46
|
+
error?.message?.includes('ETIMEDOUT') ||
|
|
47
|
+
error?.message?.includes('ECONNRESET') ||
|
|
48
|
+
(response?.status === 403 && (
|
|
49
|
+
error?.message?.toLowerCase().includes('usage limit') ||
|
|
50
|
+
error?.message?.toLowerCase().includes('rate limit') ||
|
|
51
|
+
error?.errors?.some(e => ['rateLimitExceeded', 'userRateLimitExceeded', 'calendarUsageLimitsExceeded'].includes(e.reason))
|
|
52
|
+
)) ||
|
|
53
|
+
extraRetryCheck(error, response);
|
|
54
|
+
|
|
55
|
+
if (isRetryable && i < maxRetries - 1) {
|
|
56
|
+
const isAuthError = error?.code === 401 || response?.status === 401;
|
|
57
|
+
if (isAuthError) {
|
|
58
|
+
Auth.invalidateToken();
|
|
59
|
+
syncWarn(`Authentication error (401) on ${tag}. Invalidated token and retrying immediately...`);
|
|
60
|
+
} else {
|
|
61
|
+
const jitter = Math.floor(Math.random() * 1000);
|
|
62
|
+
const totalDelay = delay + jitter;
|
|
63
|
+
syncWarn(`Retryable error on ${tag} (status: ${response?.status || error?.code}). Retrying in ${totalDelay}ms...`);
|
|
64
|
+
await sleep(totalDelay);
|
|
65
|
+
delay *= 2;
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (error) {
|
|
71
|
+
if (!skipLog(error, response)) {
|
|
72
|
+
syncError(`Failed in ${tag}`, error);
|
|
73
|
+
}
|
|
74
|
+
return { data: null, response: responseSyncify(response) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { data: response.data, response: responseSyncify(response) };
|
|
78
|
+
}
|
|
79
|
+
};
|
package/src/support/sxsheets.js
CHANGED
|
@@ -6,47 +6,16 @@
|
|
|
6
6
|
* - arguments and returns must be serializable ie. primitives or plain objects
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { syncWarn, syncError } from './workersync/synclogger.js';
|
|
9
|
+
import { sxRetry } from './sxretry.js';
|
|
11
10
|
import { getSheetsApiClient } from '../services/advsheets/shapis.js';
|
|
12
11
|
|
|
13
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
-
|
|
15
12
|
export const sxSheets = async (Auth, { subProp, prop, method, params, options }) => {
|
|
16
13
|
|
|
17
14
|
const apiClient = getSheetsApiClient();
|
|
18
|
-
const
|
|
19
|
-
let delay = 1777;
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
22
|
-
let response;
|
|
23
|
-
let error;
|
|
24
|
-
try {
|
|
25
|
-
const callish = subProp ? apiClient[prop][subProp] : apiClient[prop];
|
|
26
|
-
response = await callish[method](params, options);
|
|
27
|
-
} catch (err) {
|
|
28
|
-
error = err;
|
|
29
|
-
response = err.response;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isRetryable = [429, 500, 503].includes(response?.status) || error?.code == 429;
|
|
33
|
-
|
|
34
|
-
if (isRetryable && i < maxRetries - 1) {
|
|
35
|
-
// add a random jitter to avoid thundering herd
|
|
36
|
-
const jitter = Math.floor(Math.random() * 1000);
|
|
37
|
-
syncWarn(`Retryable error on Sheets API call ${prop}.${method} (status: ${response?.status}). Retrying in ${delay + jitter}ms...`);
|
|
38
|
-
await sleep(delay + jitter);
|
|
39
|
-
delay *= 2;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
15
|
+
const tag = `sxSheets for ${prop}.${method}`;
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
data: response.data,
|
|
49
|
-
response: responseSyncify(response)
|
|
50
|
-
};
|
|
51
|
-
}
|
|
17
|
+
return sxRetry(Auth, tag, async () => {
|
|
18
|
+
const callish = subProp ? apiClient[prop][subProp] : apiClient[prop];
|
|
19
|
+
return callish[method](params, options);
|
|
20
|
+
});
|
|
52
21
|
};
|
package/src/support/sxslides.js
CHANGED
|
@@ -6,12 +6,9 @@
|
|
|
6
6
|
* - arguments and returns must be serializable ie. primitives or plain objects
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { syncWarn, syncError } from './workersync/synclogger.js';
|
|
9
|
+
import { sxRetry } from './sxretry.js';
|
|
11
10
|
import { getSlidesApiClient } from '../services/advslides/slapis.js';
|
|
12
11
|
|
|
13
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
-
|
|
15
12
|
/**
|
|
16
13
|
* sync a call to slides api
|
|
17
14
|
* @param {object} Auth the auth object
|
|
@@ -25,37 +22,9 @@ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
25
22
|
export const sxSlides = async (Auth, { prop, method, params, options = {} }) => {
|
|
26
23
|
|
|
27
24
|
const apiClient = getSlidesApiClient();
|
|
25
|
+
const tag = `sxSlides for ${prop}.${method}`;
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
33
|
-
let response;
|
|
34
|
-
let error;
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const callish = apiClient[prop];
|
|
38
|
-
response = await callish[method](params, options);
|
|
39
|
-
} catch (err) {
|
|
40
|
-
error = err;
|
|
41
|
-
response = err.response;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const isRetryable = [429, 500, 503].includes(response?.status) || error?.code == 429;
|
|
45
|
-
|
|
46
|
-
if (isRetryable && i < maxRetries - 1) {
|
|
47
|
-
// add a random jitter to avoid thundering herd
|
|
48
|
-
const jitter = Math.floor(Math.random() * 1000);
|
|
49
|
-
syncWarn(`Retryable error on Slides API call ${prop}.${method} (status: ${response?.status}). Retrying in ${delay + jitter}ms...`);
|
|
50
|
-
await sleep(delay + jitter);
|
|
51
|
-
delay *= 2;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (error || isRetryable) {
|
|
56
|
-
syncError(`Failed in sxSlides for ${prop}.${method}`, error);
|
|
57
|
-
return { data: null, response: responseSyncify(response) };
|
|
58
|
-
}
|
|
59
|
-
return { data: response.data, response: responseSyncify(response) };
|
|
60
|
-
}
|
|
27
|
+
return sxRetry(Auth, tag, async () => {
|
|
28
|
+
return apiClient[prop][method](params, options);
|
|
29
|
+
});
|
|
61
30
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Auth } from './auth.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* fetch effective user access token
|
|
5
|
+
*/
|
|
6
|
+
export const sxGetAccessToken = async () => {
|
|
7
|
+
return await Auth.getAccessToken()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const sxGetAccessTokenInfo = async () => {
|
|
11
|
+
return await Auth.getAccessTokenInfo()
|
|
12
|
+
}
|
|
13
|
+
export const sxGetSourceAccessTokenInfo = async () => {
|
|
14
|
+
return await Auth.getSourceAccessTokenInfo()
|
|
15
|
+
}
|
package/src/support/syncit.js
CHANGED
|
@@ -270,11 +270,14 @@ const fxInit = ({
|
|
|
270
270
|
// this is the path of the runing main process
|
|
271
271
|
const mainDir = path.dirname(process.argv[1]);
|
|
272
272
|
|
|
273
|
+
// Resolve defaults relative to mainDir if they are relative
|
|
274
|
+
const resolve = (p) => path.isAbsolute(p) ? p : path.resolve(mainDir, p);
|
|
275
|
+
|
|
273
276
|
// because this is all run in a synced subprocess it's not an async result
|
|
274
277
|
const synced = callSync("sxInit", {
|
|
275
|
-
claspPath,
|
|
276
|
-
settingsPath,
|
|
277
|
-
manifestPath,
|
|
278
|
+
claspPath: resolve(claspPath),
|
|
279
|
+
settingsPath: resolve(settingsPath),
|
|
280
|
+
manifestPath: resolve(manifestPath),
|
|
278
281
|
mainDir,
|
|
279
282
|
cachePath,
|
|
280
283
|
propertiesPath,
|
|
@@ -283,22 +286,22 @@ const fxInit = ({
|
|
|
283
286
|
|
|
284
287
|
const {
|
|
285
288
|
scopes,
|
|
289
|
+
activeUser,
|
|
290
|
+
effectiveUser,
|
|
286
291
|
projectId,
|
|
287
|
-
tokenInfo,
|
|
288
|
-
accessToken,
|
|
289
292
|
settings,
|
|
290
293
|
manifest,
|
|
291
294
|
clasp,
|
|
292
295
|
} = synced;
|
|
293
296
|
|
|
294
|
-
// set these values from the subprocess
|
|
297
|
+
// set these values from the subprocess into the main project version of auth
|
|
295
298
|
Auth.setProjectId(projectId);
|
|
296
|
-
//Auth.setAuth(scopes)
|
|
297
|
-
Auth.setTokenInfo(tokenInfo);
|
|
298
|
-
//Auth.setAccessToken(accessToken)
|
|
299
299
|
Auth.setSettings(settings);
|
|
300
300
|
Auth.setClasp(clasp);
|
|
301
301
|
Auth.setManifest(manifest);
|
|
302
|
+
Auth.setActiveUser(activeUser);
|
|
303
|
+
Auth.setEffectiveUser(effectiveUser);
|
|
304
|
+
Auth.setTokenScopes(scopes);
|
|
302
305
|
return synced;
|
|
303
306
|
};
|
|
304
307
|
|
|
@@ -367,6 +370,17 @@ const fxFetchAll = (requests, responseFields) => {
|
|
|
367
370
|
return callSync("sxFetchAll", requests, responseFields);
|
|
368
371
|
};
|
|
369
372
|
|
|
373
|
+
const fxGetAccessToken = () => {
|
|
374
|
+
return callSync("sxGetAccessToken");
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const fxGetAccessTokenInfo = () => {
|
|
378
|
+
return callSync("sxGetAccessTokenInfo");
|
|
379
|
+
};
|
|
380
|
+
const fxGetSourceAccessTokenInfo = () => {
|
|
381
|
+
return callSync("sxGetSourceAccessTokenInfo");
|
|
382
|
+
};
|
|
383
|
+
|
|
370
384
|
const fxSheets = (args) =>
|
|
371
385
|
fxGeneric({
|
|
372
386
|
...args,
|
|
@@ -430,5 +444,8 @@ export const Syncit = {
|
|
|
430
444
|
fxForms,
|
|
431
445
|
fxGmail,
|
|
432
446
|
fxCalendar,
|
|
433
|
-
fxDriveExport
|
|
447
|
+
fxDriveExport,
|
|
448
|
+
fxGetAccessToken,
|
|
449
|
+
fxGetAccessTokenInfo,
|
|
450
|
+
fxGetSourceAccessTokenInfo
|
|
434
451
|
}
|
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { isQuiet } from '../slogger.js';
|
|
3
|
+
|
|
4
|
+
const isCloudRun = !!process.env.K_SERVICE;
|
|
5
|
+
const getTimestamp = () => new Date().toISOString();
|
|
6
|
+
|
|
7
|
+
const writeSyncLog = (stream, severity, message) => {
|
|
8
|
+
if (isCloudRun) {
|
|
9
|
+
const entry = {
|
|
10
|
+
severity: severity,
|
|
11
|
+
message: message,
|
|
12
|
+
timestamp: getTimestamp()
|
|
13
|
+
};
|
|
14
|
+
fs.writeSync(stream, JSON.stringify(entry) + '\n');
|
|
15
|
+
} else {
|
|
16
|
+
fs.writeSync(stream, `${getTimestamp()} ${message}\n`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
3
20
|
export const syncLog = (message) => {
|
|
4
|
-
if (!isQuiet)
|
|
21
|
+
if (!isQuiet) writeSyncLog(1, 'DEFAULT', `[Worker] ${message}`);
|
|
5
22
|
};
|
|
6
23
|
|
|
7
24
|
export const syncWarn = (message) => {
|
|
8
|
-
|
|
25
|
+
writeSyncLog(1, 'WARNING', `[Worker Warn] ${message}`);
|
|
9
26
|
};
|
|
10
27
|
|
|
11
28
|
export const syncInfo = (message) => {
|
|
12
|
-
if (!isQuiet)
|
|
29
|
+
if (!isQuiet) writeSyncLog(1, 'INFO', `[Worker Info] ${message}`);
|
|
13
30
|
};
|
|
31
|
+
|
|
14
32
|
export const syncError = (message, error) => {
|
|
15
|
-
// Providing the error object is optional
|
|
16
33
|
const errorMessage = error ? `: ${error?.stack || error}` : '';
|
|
17
|
-
|
|
34
|
+
writeSyncLog(2, 'ERROR', `[Worker Error] ${message}${errorMessage}`);
|
|
18
35
|
};
|