@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.
Files changed (72) hide show
  1. package/README.md +63 -30
  2. package/gasmess/bruce/pbx.js +53 -2
  3. package/gprompts/gas-inventory.js +92 -0
  4. package/gprompts/gas-inventory.json +176 -0
  5. package/gprompts/inventory-list.json +1 -0
  6. package/gprompts/model.json +34 -0
  7. package/gprompts/package-lock.json +1103 -0
  8. package/gprompts/package.json +18 -0
  9. package/gprompts/temp_fetch.mjs +9 -0
  10. package/gprompts/update-progress.js +142 -0
  11. package/package.json +6 -2
  12. package/setup.sh +147 -0
  13. package/src/index.js +9 -2
  14. package/src/services/advdocs/app.js +4 -23
  15. package/src/services/advdrive/app.js +6 -28
  16. package/src/services/advforms/app.js +6 -25
  17. package/src/services/advgmail/app.js +11 -0
  18. package/src/services/advgmail/fakeadvgmail.js +39 -0
  19. package/src/services/advgmail/fakeadvgmaillabels.js +119 -0
  20. package/src/services/advgmail/fakeadvgmailusers.js +23 -0
  21. package/src/services/advgmail/gmailapis.js +15 -0
  22. package/src/services/advsheets/app.js +6 -26
  23. package/src/services/advslides/app.js +6 -28
  24. package/src/services/common/lazyloader.js +22 -0
  25. package/src/services/documentapp/app.js +8 -42
  26. package/src/services/documentapp/appenderhelpers.js +21 -23
  27. package/src/services/documentapp/elementhelpers.js +0 -1
  28. package/src/services/documentapp/elementoptions.js +22 -32
  29. package/src/services/documentapp/fakeelement.js +20 -3
  30. package/src/services/documentapp/fakelistitem.js +177 -28
  31. package/src/services/documentapp/fakeparagraph.js +194 -7
  32. package/src/services/documentapp/faketable.js +16 -0
  33. package/src/services/documentapp/faketablerow.js +15 -0
  34. package/src/services/documentapp/nrhelpers.js +1 -0
  35. package/src/services/documentapp/shadowdocument.js +10 -0
  36. package/src/services/driveapp/app.js +6 -28
  37. package/src/services/enums/gmailenums.js +8 -0
  38. package/src/services/formapp/app.js +5 -40
  39. package/src/services/gmailapp/app.js +11 -0
  40. package/src/services/gmailapp/fakegmailapp.js +35 -0
  41. package/src/services/gmailapp/fakegmaillabel.js +44 -0
  42. package/src/services/logger/app.js +8 -0
  43. package/src/services/logger/fakelogger.js +162 -0
  44. package/src/services/scriptapp/app.js +7 -1
  45. package/src/services/scriptapp/behavior.js +1 -1
  46. package/src/services/session/app.js +10 -0
  47. package/src/services/slidesapp/app.js +5 -40
  48. package/src/services/spreadsheetapp/app.js +6 -50
  49. package/src/services/spreadsheetapp/fakeprotection.js +6 -7
  50. package/src/services/spreadsheetapp/fakesheet.js +3 -4
  51. package/src/services/stores/app.js +0 -1
  52. package/src/services/urlfetchapp/app.js +0 -1
  53. package/src/services/utilities/app.js +6 -20
  54. package/src/support/gmailcacher.js +7 -0
  55. package/src/support/helpers.js +2 -2
  56. package/src/support/proxies.js +20 -1
  57. package/src/support/sxgmail.js +55 -0
  58. package/src/support/syncit.js +5 -2
  59. package/src/support/utils.js +46 -15
  60. package/src/support/workersync/sxfunctions.js +5 -10
  61. package/togas.bash +18 -5
  62. package/ghissues/image-size-inconsistency-issue.sh +0 -46
  63. package/ghissues/issue-formapp-create-title-inconsistency.sh +0 -51
  64. package/ghissues/issue-positioned-image.sh +0 -25
  65. package/ghissues/post-issue.sh +0 -53
  66. package/ghissues/protection-editors-issue.sh +0 -33
  67. package/ghissues/review-sandbox-listing-issue.sh +0 -45
  68. package/ghissues/sandbox-issue.sh +0 -31
  69. package/ghissues/setup-under-construction.sh +0 -107
  70. package/src/services/base/app.js +0 -33
  71. /package/{regenerate-progress-reports.sh → gprompts/regenerate-progress-reports.sh} +0 -0
  72. /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
- console.log('...activating proxy for', name)
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 = ['DocumentApp', 'DriveApp', 'SpreadsheetApp', 'SlidesApp', 'Docs', 'Sheets', 'Drive', 'Slides', 'FormApp', 'Forms']
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 a global entry for the singleton
3
- * before we actually have everything we need to create it.
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
- * adds to global space to mimic Apps Script behavior
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
- const name = "SpreadsheetApp";
18
- const serviceName = "SpreadsheetApp";
19
-
20
- if (typeof globalThis[name] === typeof undefined) {
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 (this.object.editors?.users && this.object.editors?.users.length > 0) {
105
- const owner = DriveApp.getFileById(this.spreadsheetId)
106
- .getOwner()
107
- .getEmail();
108
- return this.object.editors.users
109
- .filter((email) => email != owner)
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 { newFakeNamedRange } from "./fakenamedrange.js";
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
- return this.getParent().getSheetByName(name);
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
@@ -103,7 +103,6 @@ if (typeof globalThis[name] === typeof undefined) {
103
103
  const getApp = () => {
104
104
  // if it hasne been intialized yet then do that
105
105
  if (!_app) {
106
- console.log('...activating proxy for', name)
107
106
  _app = {
108
107
  fetch
109
108
  }
@@ -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
- * adds to global space to mimic Apps Script behavior
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
- const name = "Utilities"
12
- if (typeof globalThis[name] === typeof undefined) {
13
- const getApp = () => {
14
- // if it hasnt been intialized yet then do that
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)
@@ -0,0 +1,7 @@
1
+ /**
2
+ * avoid going to api if we already have it
3
+ * anything other than a get should jusr delete the whiole map for the form
4
+ */
5
+ const USE_CACHE = true
6
+ import { newFetchCacher } from "./fetchcacher.js"
7
+ export const gmailCacher = newFetchCacher(USE_CACHE)
@@ -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
  }
@@ -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
+ };
@@ -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
- //Auth.setProjectId(projectId)
277
+ Auth.setProjectId(projectId)
277
278
  //Auth.setAuth(scopes)
278
- //Auth.setTokenInfo(tokenInfo)
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
  }