@mcpher/gas-fakes 1.0.19 → 1.0.21
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/README.md +63 -30
- package/gasmess/bruce/pbx.js +53 -2
- package/gprompts/gas-inventory.js +92 -0
- package/gprompts/gas-inventory.json +176 -0
- package/gprompts/inventory-list.json +1 -0
- package/gprompts/model.json +34 -0
- package/gprompts/package-lock.json +1103 -0
- package/gprompts/package.json +18 -0
- package/gprompts/temp_fetch.mjs +9 -0
- package/gprompts/update-progress.js +142 -0
- package/package.json +6 -2
- package/setup.sh +147 -0
- package/src/index.js +9 -2
- package/src/services/advdocs/app.js +4 -23
- package/src/services/advdrive/app.js +6 -28
- package/src/services/advforms/app.js +6 -25
- package/src/services/advgmail/app.js +11 -0
- package/src/services/advgmail/fakeadvgmail.js +39 -0
- package/src/services/advgmail/fakeadvgmaillabels.js +119 -0
- package/src/services/advgmail/fakeadvgmailusers.js +23 -0
- package/src/services/advgmail/gmailapis.js +15 -0
- package/src/services/advsheets/app.js +6 -26
- package/src/services/advslides/app.js +6 -28
- package/src/services/common/lazyloader.js +22 -0
- package/src/services/documentapp/app.js +8 -42
- package/src/services/documentapp/appenderhelpers.js +21 -23
- package/src/services/documentapp/elementhelpers.js +0 -1
- package/src/services/documentapp/elementoptions.js +22 -32
- package/src/services/documentapp/fakeelement.js +20 -3
- package/src/services/documentapp/fakelistitem.js +177 -28
- package/src/services/documentapp/fakeparagraph.js +194 -7
- package/src/services/documentapp/faketable.js +16 -0
- package/src/services/documentapp/faketablerow.js +15 -0
- package/src/services/documentapp/nrhelpers.js +1 -0
- package/src/services/documentapp/shadowdocument.js +10 -0
- package/src/services/driveapp/app.js +6 -28
- package/src/services/enums/gmailenums.js +8 -0
- package/src/services/formapp/app.js +5 -40
- package/src/services/gmailapp/app.js +11 -0
- package/src/services/gmailapp/fakegmailapp.js +35 -0
- package/src/services/gmailapp/fakegmaillabel.js +44 -0
- package/src/services/logger/app.js +8 -0
- package/src/services/logger/fakelogger.js +162 -0
- package/src/services/scriptapp/app.js +7 -1
- package/src/services/scriptapp/behavior.js +1 -1
- package/src/services/session/app.js +10 -0
- package/src/services/slidesapp/app.js +5 -40
- package/src/services/spreadsheetapp/app.js +6 -50
- package/src/services/spreadsheetapp/fakeprotection.js +6 -7
- package/src/services/spreadsheetapp/fakesheet.js +3 -4
- package/src/services/stores/app.js +0 -1
- package/src/services/urlfetchapp/app.js +0 -1
- package/src/services/utilities/app.js +6 -20
- package/src/support/gmailcacher.js +7 -0
- package/src/support/helpers.js +2 -2
- package/src/support/proxies.js +20 -1
- package/src/support/sxgmail.js +55 -0
- package/src/support/syncit.js +5 -2
- package/src/support/utils.js +46 -15
- package/src/support/workersync/sxfunctions.js +5 -10
- package/togas.bash +18 -5
- package/ghissues/image-size-inconsistency-issue.sh +0 -46
- package/ghissues/issue-formapp-create-title-inconsistency.sh +0 -51
- package/ghissues/issue-positioned-image.sh +0 -25
- package/ghissues/post-issue.sh +0 -53
- package/ghissues/protection-editors-issue.sh +0 -33
- package/ghissues/review-sandbox-listing-issue.sh +0 -45
- package/ghissues/sandbox-issue.sh +0 -31
- package/ghissues/setup-under-construction.sh +0 -107
- package/src/services/base/app.js +0 -33
- /package/{regenerate-progress-reports.sh → gprompts/regenerate-progress-reports.sh} +0 -0
- /package/src/services/{base → session}/fakesession.js +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* the idea here is to create an empty global entry for the singleton
|
|
5
|
+
* but only load it when it is actually used.
|
|
6
|
+
*/
|
|
7
|
+
import { newFakeGmailApp as maker } from './fakegmailapp.js';
|
|
8
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
9
|
+
|
|
10
|
+
let _app = null;
|
|
11
|
+
_app = lazyLoaderApp(_app, 'GmailApp', maker)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { newFakeGmailLabel } from './fakegmaillabel.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides access to Gmail threads, messages, and labels.
|
|
6
|
+
*/
|
|
7
|
+
class FakeGmailApp {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.__fakeObjectType = 'GmailApp';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new user label.
|
|
14
|
+
* @param {string} name The name of the new label.
|
|
15
|
+
* @returns {GmailLabel} The new label.
|
|
16
|
+
*/
|
|
17
|
+
createLabel(name) {
|
|
18
|
+
const newLabelResource = Gmail.newLabel().setName(name);
|
|
19
|
+
const createdLabelResource = Gmail.Users.Labels.create(newLabelResource, 'me');
|
|
20
|
+
return newFakeGmailLabel(createdLabelResource);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets a list of user-created labels.
|
|
25
|
+
* @returns {GmailLabel[]} An array of user-created labels.
|
|
26
|
+
*/
|
|
27
|
+
getUserLabels() {
|
|
28
|
+
const { labels } = Gmail.Users.Labels.list('me');
|
|
29
|
+
// The documentation for GmailApp.getUserLabels() says "user-created labels".
|
|
30
|
+
// The live environment follows this, so we will filter for type 'user'.
|
|
31
|
+
return labels ? labels.filter(l => l.type === 'user').map(labelResource => newFakeGmailLabel(labelResource)) : [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const newFakeGmailApp = (...args) => Proxies.guard(new FakeGmailApp(...args));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
|
|
3
|
+
export const newFakeGmailLabel = (...args) => Proxies.guard(new FakeGmailLabel(...args));
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A user-created label in a user's Gmail account.
|
|
7
|
+
*/
|
|
8
|
+
class FakeGmailLabel {
|
|
9
|
+
constructor(labelResource) {
|
|
10
|
+
this.__labelResource = labelResource;
|
|
11
|
+
this.__fakeObjectType = 'GmailLabel';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Deletes this label.
|
|
16
|
+
*/
|
|
17
|
+
deleteLabel() {
|
|
18
|
+
Gmail.Users.Labels.remove('me', this.getId());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gets the ID of this label.
|
|
23
|
+
* @returns {string} The label ID.
|
|
24
|
+
*/
|
|
25
|
+
getId() {
|
|
26
|
+
return this.__labelResource.id;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the name of this label.
|
|
31
|
+
* @returns {string} The label name.
|
|
32
|
+
*/
|
|
33
|
+
getName() {
|
|
34
|
+
return this.__labelResource.name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the name of the label.
|
|
39
|
+
* @returns {string} The name of the label.
|
|
40
|
+
*/
|
|
41
|
+
toString() {
|
|
42
|
+
return 'GmailLabel';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* the idea here is to create an empty global entry for the singleton
|
|
3
|
+
* but only load it when it is actually used.
|
|
4
|
+
*/
|
|
5
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
6
|
+
import { newFakeLogger as maker } from './fakelogger.js';
|
|
7
|
+
let _app = null;
|
|
8
|
+
_app = lazyLoaderApp(_app, 'Logger', maker)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { format } from 'util';
|
|
2
|
+
import { Proxies } from '../../support/proxies.js';
|
|
3
|
+
import { Logging } from '@google-cloud/logging';
|
|
4
|
+
|
|
5
|
+
// --- Cloud Logging Integration ---
|
|
6
|
+
|
|
7
|
+
// Cloud Logging client instance.
|
|
8
|
+
export let cloudLog;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @class
|
|
12
|
+
* @implements {GoogleAppsScript.Base.Logger}
|
|
13
|
+
*/
|
|
14
|
+
export class FakeLogger {
|
|
15
|
+
constructor() {
|
|
16
|
+
/**
|
|
17
|
+
* @private
|
|
18
|
+
* @type {string[]}
|
|
19
|
+
*/
|
|
20
|
+
this.__log = [];
|
|
21
|
+
/**
|
|
22
|
+
* @private
|
|
23
|
+
* @type {string}
|
|
24
|
+
*/
|
|
25
|
+
this.__destination = (process.env.LOG_DESTINATION || 'CONSOLE').toUpperCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Clears the log.
|
|
30
|
+
*/
|
|
31
|
+
clear() {
|
|
32
|
+
this.__log = [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the log contents.
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
getLog() {
|
|
40
|
+
return this.__log.join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Logs a message.
|
|
45
|
+
* @param {string} formatOrData
|
|
46
|
+
* @param {...any} values
|
|
47
|
+
* @returns {FakeLogger}
|
|
48
|
+
*/
|
|
49
|
+
log(formatOrData, ...values) {
|
|
50
|
+
// Check the instance's destination property.
|
|
51
|
+
if (this.__destination === 'NONE') {
|
|
52
|
+
// Still push to the internal log for getLog(), but don't output.
|
|
53
|
+
const message = format(formatOrData, ...values);
|
|
54
|
+
this.__log.push(message);
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const message = format(formatOrData, ...values);
|
|
59
|
+
this.__log.push(message);
|
|
60
|
+
|
|
61
|
+
// Pass the destination and instance context to the writer function.
|
|
62
|
+
writeToCloudOrConsole(message, this);
|
|
63
|
+
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns 'Logger'.
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
toString() {
|
|
72
|
+
return 'Logger';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sets the logging destination dynamically.
|
|
77
|
+
* @param {string} destination - The destination ('CONSOLE', 'CLOUD', 'BOTH', 'NONE').
|
|
78
|
+
* @returns {FakeLogger}
|
|
79
|
+
*/
|
|
80
|
+
__setLogDestination(destination) {
|
|
81
|
+
if (['CONSOLE', 'CLOUD', 'BOTH', 'NONE'].includes(destination.toUpperCase())) {
|
|
82
|
+
this.__destination = destination.toUpperCase();
|
|
83
|
+
}
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @returns {FakeLogger}
|
|
90
|
+
*/
|
|
91
|
+
export const newFakeLogger = (...args) => Proxies.guard(new FakeLogger(...args));
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Writes a log message to the configured destination (Cloud Logging or console).
|
|
95
|
+
* @param {string} message The log message.
|
|
96
|
+
* @private
|
|
97
|
+
* @param {FakeLogger} loggerInstance The logger instance, to access its destination.
|
|
98
|
+
*/
|
|
99
|
+
const writeToCloudOrConsole = (message, loggerInstance) => {
|
|
100
|
+
const logDestination = loggerInstance.__destination;
|
|
101
|
+
const useConsoleLogging = logDestination === 'CONSOLE' || logDestination === 'BOTH';
|
|
102
|
+
const useCloudLogging = logDestination === 'CLOUD' || logDestination === 'BOTH';
|
|
103
|
+
|
|
104
|
+
// Lazy-initialize the cloud logger if needed.
|
|
105
|
+
const initializeCloudLogging = () => {
|
|
106
|
+
if (cloudLog) return; // Already initialized.
|
|
107
|
+
try {
|
|
108
|
+
const projectId = ScriptApp.__projectId;
|
|
109
|
+
if (!projectId) {
|
|
110
|
+
throw new Error('Could not determine Google Cloud Project ID for logging.');
|
|
111
|
+
}
|
|
112
|
+
const logging = new Logging({ projectId });
|
|
113
|
+
const logName = 'gas-fakes/console_logs';
|
|
114
|
+
cloudLog = logging.log(logName);
|
|
115
|
+
console.info(`gas-fakes: Cloud Logging is enabled, writing to log "${logName}".`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.warn(
|
|
118
|
+
`gas-fakes: Cloud Logging failed to initialize. ` +
|
|
119
|
+
'Falling back to console.log.',
|
|
120
|
+
err.message
|
|
121
|
+
);
|
|
122
|
+
cloudLog = null;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Write to console if CONSOLE or BOTH is specified.
|
|
127
|
+
if (useConsoleLogging) {
|
|
128
|
+
// Write structured JSON to stdout for agent-based collection or local viewing.
|
|
129
|
+
const logEntry = {
|
|
130
|
+
message: message,
|
|
131
|
+
severity: 'INFO',
|
|
132
|
+
};
|
|
133
|
+
console.log(JSON.stringify(logEntry));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Write to Cloud Logging if CLOUD or BOTH is specified and client is ready.
|
|
137
|
+
if (useCloudLogging) {
|
|
138
|
+
// Initialize the cloud logger on the first use if it hasn't been already.
|
|
139
|
+
if (!cloudLog) {
|
|
140
|
+
initializeCloudLogging();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const metadata = {
|
|
144
|
+
// Use the 'global' resource type, which is a generic fallback for applications
|
|
145
|
+
// not running on a specific GCP compute service.
|
|
146
|
+
resource: { type: 'global' },
|
|
147
|
+
labels: {
|
|
148
|
+
'gas-fakes-scriptId': ScriptApp.getScriptId(),
|
|
149
|
+
'gas-fakes-userId': ScriptApp.__userId,
|
|
150
|
+
},
|
|
151
|
+
jsonPayload: { message: message },
|
|
152
|
+
severity: 'INFO',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Use log.write which returns a promise. T
|
|
156
|
+
// his allows us to catch errors, such as a disabled API or permission issues.
|
|
157
|
+
// however we are firing and forgetting so an error might appear later on in execution
|
|
158
|
+
if (cloudLog) {
|
|
159
|
+
cloudLog.write(cloudLog.entry(metadata)).catch(err=> console.error('gas-fakes: Failed to write to Cloud Logging.', err.message))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
@@ -110,12 +110,18 @@ if (typeof globalThis[name] === typeof undefined) {
|
|
|
110
110
|
|
|
111
111
|
// if it hasn't been intialized yet then do that
|
|
112
112
|
if (!_app) {
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
_app = {
|
|
115
115
|
getOAuthToken,
|
|
116
116
|
requireAllScopes,
|
|
117
117
|
requireScopes,
|
|
118
118
|
getScriptId: Auth.getScriptId,
|
|
119
|
+
get __projectId () {
|
|
120
|
+
return Auth.getProjectId()
|
|
121
|
+
},
|
|
122
|
+
get __userId () {
|
|
123
|
+
return Auth.getUserId()
|
|
124
|
+
},
|
|
119
125
|
AuthMode: {
|
|
120
126
|
FULL: 'FULL'
|
|
121
127
|
},
|
|
@@ -167,7 +167,7 @@ class FakeBehavior {
|
|
|
167
167
|
this.__idWhitelist = null
|
|
168
168
|
|
|
169
169
|
// individually settable services
|
|
170
|
-
const services =
|
|
170
|
+
const services = ScriptApp.__registeredServices
|
|
171
171
|
this.__sandboxService = {}
|
|
172
172
|
services.forEach(f => this.__sandboxService[f] = newFakeSandboxService(this, f))
|
|
173
173
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* the idea here is to create an empty global entry for the singleton
|
|
4
|
+
* but only load it when it is actually used.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
8
|
+
import { newFakeSession as maker} from './fakesession.js'
|
|
9
|
+
let _app = null;
|
|
10
|
+
_app = lazyLoaderApp(_app, 'Session', maker)
|
|
@@ -1,44 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* the idea here is to create
|
|
3
|
-
*
|
|
4
|
-
* We do this by using a proxy, intercepting calls to the
|
|
5
|
-
* initial sigleton and diverting them to a completed one
|
|
2
|
+
* the idea here is to create an empty global entry for the singleton
|
|
3
|
+
* but only load it when it is actually used.
|
|
6
4
|
*/
|
|
7
|
-
import { newFakeSlidesApp } from './fakeslidesapp.js';
|
|
8
|
-
import { Proxies } from '../../support/proxies.js';
|
|
9
5
|
|
|
6
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
7
|
+
import { newFakeSlidesApp as maker} from './fakeslidesapp.js';
|
|
10
8
|
let _app = null;
|
|
11
|
-
|
|
12
|
-
const name = 'SlidesApp';
|
|
13
|
-
const serviceName = 'SlidesApp';
|
|
14
|
-
|
|
15
|
-
if (typeof globalThis[name] === typeof undefined) {
|
|
16
|
-
const getApp = () => {
|
|
17
|
-
if (!_app) {
|
|
18
|
-
const realApp = newFakeSlidesApp();
|
|
19
|
-
|
|
20
|
-
_app = new Proxy(realApp, {
|
|
21
|
-
get(target, prop, receiver) {
|
|
22
|
-
if (prop === 'toString') {
|
|
23
|
-
return () => name;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const serviceBehavior = ScriptApp.__behavior.sandboxService[serviceName];
|
|
27
|
-
|
|
28
|
-
if (!serviceBehavior.enabled) {
|
|
29
|
-
throw new Error(`${name} service is disabled by sandbox settings.`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const allowedMethods = serviceBehavior.methods;
|
|
33
|
-
if (allowedMethods && typeof target[prop] === 'function' && !allowedMethods.includes(prop)) {
|
|
34
|
-
throw new Error(`Method ${name}.${prop} is not allowed by sandbox settings.`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return Reflect.get(...arguments);
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return _app;
|
|
42
|
-
};
|
|
43
|
-
Proxies.registerProxy(name, getApp);
|
|
44
|
-
}
|
|
9
|
+
_app = lazyLoaderApp(_app, 'SlidesApp', maker)
|
|
@@ -1,53 +1,9 @@
|
|
|
1
|
-
// fake Apps Script SpreadsheetApp
|
|
2
|
-
/**
|
|
3
|
-
* the idea here is to create a global entry for the singleton
|
|
4
|
-
* before we actually have everything we need to create it.
|
|
5
|
-
* We do this by using a proxy, intercepting calls to the
|
|
6
|
-
* initial sigleton and diverting them to a completed one
|
|
7
|
-
*/
|
|
8
|
-
import { newFakeSpreadsheetApp } from './fakespreadsheetapp.js'
|
|
9
|
-
import { Proxies } from '../../support/proxies.js'
|
|
10
|
-
|
|
11
|
-
// This will eventually hold a proxy for DriveApp
|
|
12
|
-
let _app = null;
|
|
13
1
|
|
|
14
2
|
/**
|
|
15
|
-
*
|
|
3
|
+
* the idea here is to create an empty global entry for the singleton
|
|
4
|
+
* but only load it when it is actually used.
|
|
16
5
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const getApp = () => {
|
|
22
|
-
// if it hasnt been intialized yet then do that
|
|
23
|
-
if (!_app) {
|
|
24
|
-
const realApp = newFakeSpreadsheetApp();
|
|
25
|
-
|
|
26
|
-
_app = new Proxy(realApp, {
|
|
27
|
-
get(target, prop, receiver) {
|
|
28
|
-
// toString on the proxy target itself
|
|
29
|
-
if (prop === 'toString') {
|
|
30
|
-
return () => name;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const serviceBehavior = ScriptApp.__behavior.sandboxService[serviceName];
|
|
34
|
-
|
|
35
|
-
if (!serviceBehavior.enabled) {
|
|
36
|
-
throw new Error(`${name} service is disabled by sandbox settings.`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const allowedMethods = serviceBehavior.methods;
|
|
40
|
-
if (allowedMethods && typeof target[prop] === 'function' && !allowedMethods.includes(prop)) {
|
|
41
|
-
throw new Error(`Method ${name}.${prop} is not allowed by sandbox settings.`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return Reflect.get(...arguments);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
// this is the actual driveApp we'll return from the proxy
|
|
49
|
-
return _app
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
Proxies.registerProxy(name, getApp)
|
|
53
|
-
}
|
|
6
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
7
|
+
import { newFakeSpreadsheetApp as maker} from './fakespreadsheetapp.js'
|
|
8
|
+
let _app = null;
|
|
9
|
+
_app = lazyLoaderApp(_app, 'SpreadsheetApp', maker)
|
|
@@ -101,13 +101,12 @@ export class FakeProtection {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
getEditors() {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.map((email) => newFakeUser({ email }));
|
|
104
|
+
if (
|
|
105
|
+
this.object.editors?.users &&
|
|
106
|
+
this.object.editors?.users.length > 0 &&
|
|
107
|
+
!this.object.warningOnly
|
|
108
|
+
) {
|
|
109
|
+
return this.object.editors.users.map((email) => newFakeUser({ email }));
|
|
111
110
|
}
|
|
112
111
|
return [];
|
|
113
112
|
}
|
|
@@ -11,11 +11,9 @@ import { newFakeDeveloperMetadataFinder } from "./fakedevelopermetadatafinder.js
|
|
|
11
11
|
import { newFakeSheetRangeList } from "./fakesheetrangelist.js";
|
|
12
12
|
import { FakeTextFinder, newFakeTextFinder } from "./faketextfinder.js";
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { newFakeNamedRange } from "./fakenamedrange.js";
|
|
15
15
|
import { newFakeProtection } from "./fakeprotection.js";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
17
|
const { is, isEnum } = Utils;
|
|
20
18
|
|
|
21
19
|
export const newFakeSheet = (properties, parent) => {
|
|
@@ -1009,7 +1007,8 @@ export class FakeSheet {
|
|
|
1009
1007
|
],
|
|
1010
1008
|
};
|
|
1011
1009
|
this.__batchUpdate(obj);
|
|
1012
|
-
|
|
1010
|
+
this.__properties.title = name;
|
|
1011
|
+
return this;
|
|
1013
1012
|
}
|
|
1014
1013
|
|
|
1015
1014
|
protect() {
|
|
@@ -26,7 +26,6 @@ const registerApp = (_app, name, kind) => {
|
|
|
26
26
|
const getApp = () => {
|
|
27
27
|
// if it hasnt been intialized yet then do that
|
|
28
28
|
if (!_app) {
|
|
29
|
-
console.log('...activating proxy for', name)
|
|
30
29
|
_app = newFakeService(kind)
|
|
31
30
|
}
|
|
32
31
|
// this is the actual driveApp we'll return from the proxy
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
import { Proxies } from '../../support/proxies.js'
|
|
2
|
-
import { newFakeUtilities } from './fakeutilities.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// This will eventually hold a proxy for Utilities
|
|
6
|
-
let _app = null
|
|
7
1
|
|
|
8
2
|
/**
|
|
9
|
-
*
|
|
3
|
+
* the idea here is to create an empty global entry for the singleton
|
|
4
|
+
* but only load it when it is actually used.
|
|
10
5
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!_app) {
|
|
16
|
-
console.log('...activating proxy for', name)
|
|
17
|
-
_app = newFakeUtilities()
|
|
18
|
-
}
|
|
19
|
-
// this is the actual driveApp we'll return from the proxy
|
|
20
|
-
return _app
|
|
21
|
-
}
|
|
22
|
-
Proxies.registerProxy(name, getApp)
|
|
23
|
-
}
|
|
6
|
+
import { lazyLoaderApp } from '../common/lazyloader.js'
|
|
7
|
+
import { newFakeUtilities as maker } from './fakeutilities.js';
|
|
8
|
+
let _app = null;
|
|
9
|
+
_app = lazyLoaderApp(_app, 'Utilities', maker)
|
package/src/support/helpers.js
CHANGED
|
@@ -208,9 +208,9 @@ export const unimplementedProps = (self, props) => {
|
|
|
208
208
|
})
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
export const gError = (response, service, method) => {
|
|
211
|
+
export const gError = (response, service, method, throw404 = false) => {
|
|
212
212
|
if (response && response.error) {
|
|
213
|
-
if (response.error.code !== 404) {
|
|
213
|
+
if (throw404 || response.error.code !== 404) {
|
|
214
214
|
throw new Error(`GoogleJsonResponseException: API call to ${service}.${method} failed with error: ${response?.error?.message}`);
|
|
215
215
|
}
|
|
216
216
|
}
|
package/src/support/proxies.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Utils } from './utils.js';
|
|
2
2
|
|
|
3
|
+
const serviceRegistry = new Set();
|
|
4
|
+
const loadedRegistry = new Set();
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* diverts the property get to another object returned by the getApp function
|
|
@@ -10,10 +12,24 @@ const getAppHandler = (getApp, name) => {
|
|
|
10
12
|
return {
|
|
11
13
|
|
|
12
14
|
get(_, prop, receiver) {
|
|
15
|
+
|
|
13
16
|
// this will let the caller know we're not really running in Apps Script
|
|
17
|
+
// every service can return this
|
|
14
18
|
if (prop === 'isFake') return true;
|
|
15
19
|
|
|
20
|
+
// this returns all services already registered - may be useful
|
|
21
|
+
if (prop === '__registeredServices') return Array.from(serviceRegistry)
|
|
22
|
+
|
|
23
|
+
// this returns all services already loaded - may be useful
|
|
24
|
+
if (prop === '__loadedServices') return Array.from(loadedRegistry)
|
|
25
|
+
|
|
26
|
+
// this will get the app and lazy load it if its not already loaded
|
|
16
27
|
const app = getApp();
|
|
28
|
+
|
|
29
|
+
// now we have a loaded service
|
|
30
|
+
loadedRegistry.add(name)
|
|
31
|
+
|
|
32
|
+
// are we being asked to run a method?
|
|
17
33
|
const member = Reflect.get(app, prop, receiver);
|
|
18
34
|
|
|
19
35
|
// Check method whitelist if it's a function call on a service
|
|
@@ -36,7 +52,9 @@ const getAppHandler = (getApp, name) => {
|
|
|
36
52
|
}
|
|
37
53
|
}
|
|
38
54
|
|
|
55
|
+
// keep a note of which services have been registered
|
|
39
56
|
const registerProxy = (name, getApp) => {
|
|
57
|
+
serviceRegistry.add(name);
|
|
40
58
|
const value = new Proxy({}, getAppHandler(getApp, name))
|
|
41
59
|
// add it to the global space to mimic what apps script does
|
|
42
60
|
// console.log (`setting ${name} to global`)
|
|
@@ -97,5 +115,6 @@ export const Proxies = {
|
|
|
97
115
|
getAppHandler,
|
|
98
116
|
registerProxy,
|
|
99
117
|
guard,
|
|
100
|
-
blanketProxy
|
|
118
|
+
blanketProxy,
|
|
119
|
+
getRegisteredServices: () => Array.from(serviceRegistry),
|
|
101
120
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GMAIL
|
|
3
|
+
* all these functions run in the worker
|
|
4
|
+
* thus turning async operations into sync
|
|
5
|
+
* note
|
|
6
|
+
* - arguments and returns must be serializable ie. primitives or plain objects
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { responseSyncify } from './auth.js';
|
|
10
|
+
import { syncWarn, syncError } from './workersync/synclogger.js';
|
|
11
|
+
import { getGmailApiClient } from '../services/advgmail/gmailapis.js';
|
|
12
|
+
|
|
13
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
+
|
|
15
|
+
export const sxGmail = async (Auth, { subProp, prop, method, params, options }) => {
|
|
16
|
+
|
|
17
|
+
const apiClient = getGmailApiClient();
|
|
18
|
+
const maxRetries = 7;
|
|
19
|
+
let delay = 1777;
|
|
20
|
+
const methodName = subProp ? `${prop}.${subProp}.${method}` : `${prop}.${method}`;
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
23
|
+
let response;
|
|
24
|
+
let error;
|
|
25
|
+
try {
|
|
26
|
+
const callish = subProp ? apiClient[prop][subProp] : apiClient[prop];
|
|
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) {
|
|
44
|
+
// Don't log 404 as an error, it's an expected outcome for some tests (e.g., get deleted item)
|
|
45
|
+
if (response?.status !== 404) {
|
|
46
|
+
syncError(`Failed in sxGmail for ${methodName}`, error);
|
|
47
|
+
}
|
|
48
|
+
return { data: null, response: responseSyncify(response) };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
data: response.data,
|
|
52
|
+
response: responseSyncify(response)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
package/src/support/syncit.js
CHANGED
|
@@ -9,6 +9,7 @@ import { mergeParamStrings } from './utils.js'
|
|
|
9
9
|
import { improveFileCache, checkResponse, getFromFileCache } from "./filecache.js"
|
|
10
10
|
import { checkResponseCacher } from './fetchcacher.js';
|
|
11
11
|
import { docsCacher } from "./docscacher.js"
|
|
12
|
+
import { gmailCacher } from './gmailcacher.js';
|
|
12
13
|
import { formsCacher } from './formscacher.js';
|
|
13
14
|
import { slidesCacher } from './slidescacher.js';
|
|
14
15
|
import { sheetsCacher } from './sheetscacher.js';
|
|
@@ -273,9 +274,9 @@ const fxInit = ({
|
|
|
273
274
|
} = synced
|
|
274
275
|
|
|
275
276
|
// set these values from the subprocess for the main project
|
|
276
|
-
|
|
277
|
+
Auth.setProjectId(projectId)
|
|
277
278
|
//Auth.setAuth(scopes)
|
|
278
|
-
|
|
279
|
+
Auth.setTokenInfo(tokenInfo)
|
|
279
280
|
//Auth.setAdcPath(adcPath)
|
|
280
281
|
//Auth.setAccessToken(accessToken)
|
|
281
282
|
Auth.setSettings(settings)
|
|
@@ -345,6 +346,7 @@ const fxSheets = (args) => fxGeneric({ ...args, serviceName: 'Sheets', cacher: s
|
|
|
345
346
|
const fxSlides = (args) => fxGeneric({ ...args, serviceName: 'Slides', cacher: slidesCacher, idField: 'presentationId' });
|
|
346
347
|
const fxDocs = (args) => fxGeneric({ ...args, serviceName: 'Docs', cacher: docsCacher, idField: 'documentId' });
|
|
347
348
|
const fxForms = (args) => fxGeneric({ ...args, serviceName: 'Forms', cacher: formsCacher, idField: 'formId' });
|
|
349
|
+
const fxGmail = (args) => fxGeneric({ ...args, serviceName: 'Gmail', cacher: gmailCacher, idField: 'id' });
|
|
348
350
|
|
|
349
351
|
export const Syncit = {
|
|
350
352
|
fxFetch,
|
|
@@ -361,4 +363,5 @@ export const Syncit = {
|
|
|
361
363
|
fxRefreshToken,
|
|
362
364
|
fxDocs,
|
|
363
365
|
fxForms,
|
|
366
|
+
fxGmail,
|
|
364
367
|
}
|