@mcpher/gas-fakes 1.0.14 → 1.0.15

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 (75) hide show
  1. package/README.RU.md +20 -1
  2. package/README.md +2 -1
  3. package/gasmess/bruce/d.js +4 -0
  4. package/gasmess/bruce/d1.js +4 -0
  5. package/gasmess/bruce/dreport.js +81 -0
  6. package/gasmess/bruce/pbx.js +33 -45
  7. package/gasmess/tanaike/for_simple_test.js +7 -1
  8. package/gasmess/tanaike/sample4.js +59 -0
  9. package/ghissues/sandbox-issue.sh +31 -0
  10. package/package.json +10 -6
  11. package/src/services/advdocs/docapis.js +10 -6
  12. package/src/services/advdocs/fakeadvdocs.js +1 -3
  13. package/src/services/advdocs/fakeadvdocuments.js +18 -3
  14. package/src/services/advdrive/drapis.js +10 -7
  15. package/src/services/advdrive/fakeadvdrive.js +19 -2
  16. package/src/services/advdrive/fakeadvdrivefiles.js +8 -5
  17. package/src/services/advdrive/fakeadvdrivepermissions.js +4 -4
  18. package/src/services/advsheets/fakeadvsheets.js +15 -5
  19. package/src/services/advsheets/fakeadvsheetsdevelopermetadata.js +3 -2
  20. package/src/services/advsheets/fakeadvsheetsspreadsheets.js +5 -6
  21. package/src/services/advsheets/fakeadvsheetsvalues.js +3 -2
  22. package/src/services/advsheets/shapis.js +18 -8
  23. package/src/services/advslides/fakeadvslides.js +20 -2
  24. package/src/services/advslides/slapis.js +10 -6
  25. package/src/services/documentapp/appenderhelpers.js +78 -17
  26. package/src/services/documentapp/elementblasters.js +61 -13
  27. package/src/services/documentapp/elementhelpers.js +38 -1
  28. package/src/services/documentapp/elementoptions.js +95 -46
  29. package/src/services/documentapp/elements.js +3 -0
  30. package/src/services/documentapp/fakebody.js +21 -7
  31. package/src/services/documentapp/fakebookmark.js +96 -0
  32. package/src/services/documentapp/fakecontainerelement.js +17 -12
  33. package/src/services/documentapp/fakedocument.js +111 -5
  34. package/src/services/documentapp/fakedocumentapp.js +3 -2
  35. package/src/services/documentapp/fakedocumenttab.js +2 -2
  36. package/src/services/documentapp/fakeelement.js +13 -8
  37. package/src/services/documentapp/fakefootersection.js +44 -0
  38. package/src/services/documentapp/fakeheadersection.js +44 -0
  39. package/src/services/documentapp/fakehorizontalrule.js +2 -2
  40. package/src/services/documentapp/fakelistitem.js +188 -0
  41. package/src/services/documentapp/fakepagebreak.js +2 -2
  42. package/src/services/documentapp/fakeparagraph.js +4 -4
  43. package/src/services/documentapp/fakeposition.js +84 -0
  44. package/src/services/documentapp/fakesectionelement.js +156 -0
  45. package/src/services/documentapp/faketable.js +28 -2
  46. package/src/services/documentapp/faketablecell.js +2 -2
  47. package/src/services/documentapp/faketablerow.js +2 -2
  48. package/src/services/documentapp/faketext.js +2 -2
  49. package/src/services/documentapp/fakeui.js +27 -0
  50. package/src/services/documentapp/nrhelpers.js +54 -19
  51. package/src/services/documentapp/shadowdocument.js +120 -40
  52. package/src/services/driveapp/driveiterators.js +8 -2
  53. package/src/services/driveapp/fakefolderapp.js +1 -0
  54. package/src/services/scriptapp/app.js +6 -6
  55. package/src/services/scriptapp/behavior.js +93 -0
  56. package/src/services/spreadsheetapp/fakenamedrange.js +120 -0
  57. package/src/services/spreadsheetapp/fakesheet.js +40 -2
  58. package/src/services/spreadsheetapp/fakespreadsheet.js +86 -3
  59. package/src/services/urlfetchapp/app.js +0 -1
  60. package/src/support/auth.js +14 -20
  61. package/src/support/backoff.js +52 -0
  62. package/src/support/sxauth.js +9 -11
  63. package/src/support/sxdocs.js +7 -9
  64. package/src/support/sxdrive.js +10 -14
  65. package/src/support/sxfetch.js +4 -3
  66. package/src/support/sxsheets.js +8 -8
  67. package/src/support/sxslides.js +8 -8
  68. package/src/support/sxstore.js +1 -1
  69. package/src/support/sxzip.js +1 -1
  70. package/src/support/syncit.js +5 -5
  71. package/src/support/utils.js +21 -0
  72. package/src/support/workersync/worker.js +5 -3
  73. package/shadowhelpers.js +0 -0
  74. package/src/services/advdrive/fakeadvvalues.js +0 -0
  75. package/src/services/documentapp/shadow.js +0 -121
package/README.RU.md CHANGED
@@ -312,4 +312,23 @@ const getParentsIterator = ({
312
312
 
313
313
  ## Помощь
314
314
 
315
- Как я уже упоминал ранее, чтобы развивать это дальше, мне понадобится много помощи для расширения поддерживаемых методов и сервисов - поэтому, если вы считаете, что это будет полезно для вас, и хотите сотрудничать, пожалуйста, свяжитесь со мной по [bruce@mcpher.com](mailto:bruce@mcpher.com) и мы поговорим.
315
+ Как я уже упоминал ранее, чтобы развивать это дальше, мне понадобится много помощи для расширения поддерживаемых методов и сервисов - поэтому, если вы считаете, что это будет полезно для вас, и хотите сотрудничать, пожалуйста, свяжитесь со мной по [bruce@mcpher.com](mailto:bruce@mcpher.com) и мы поговорим.
316
+
317
+
318
+ ## Translations and writeups
319
+
320
+ - [initial idea and thoughts](https://ramblings.mcpher.com/a-proof-of-concept-implementation-of-apps-script-environment-on-node/)
321
+ - [Inside the volatile world of a Google Document](https://ramblings.mcpher.com/inside-the-volatile-world-of-a-google-document/
322
+ - [Apps Script Services on Node – using apps script libraries](https://ramblings.mcpher.com/apps-script-services-on-node-using-apps-script-libraries/)
323
+ - [Apps Script environment on Node – more services](https://ramblings.mcpher.com/apps-script-environment-on-node-more-services/)
324
+ - [Turning async into synch on Node using workers](https://ramblings.mcpher.com/turning-async-into-synch-on-node-using-workers/)
325
+ - [All about Apps Script Enums and how to fake them](https://ramblings.mcpher.com/all-about-apps-script-enums-and-how-to-fake-them/)
326
+ - [Russian version](README.RU.md) ([credit Alex Ivanov](https://github.com/oshliaer)) - needs updating
327
+ - [colaborators](collaborators.md) - additional information for collaborators
328
+ - [oddities](oddities.md) - a collection of oddities uncovered during this project
329
+ - [gemini](gemini.md) - some reflections and experiences on using gemini to help code large projects
330
+ - [named colors](named-colors.md) - colors supported by Apps Script
331
+ - [setup env](setup-env.md) - ([credit Eric Shapiro] - additional info on contents of .env file
332
+ - [this file](README.md)
333
+ - [named colors](named-colors.md)
334
+ - [sandbox](sandbox.md)
package/README.md CHANGED
@@ -143,4 +143,5 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
143
143
  - [named colors](named-colors.md) - colors supported by Apps Script
144
144
  - [setup env](setup-env.md) - ([credit Eric Shapiro] - additional info on contents of .env file
145
145
  - [this file](README.md)
146
- - [named colors](named-colors.md)
146
+ - [named colors](named-colors.md)
147
+ - [sandbox](sandbox.md)
@@ -0,0 +1,4 @@
1
+ import '../../main.js';
2
+ const res1 = Sheets.Spreadsheets.create({ properties: { title: "sample" } });
3
+ const res2 = Sheets.Spreadsheets.get(res1.spreadsheetId);
4
+ console.log(res2);
@@ -0,0 +1,4 @@
1
+ import '../../main.js';
2
+ const d = DriveApp.createFile('my-test-file.txt', 'Hello from gas-fakes!');
3
+ const e = DriveApp.getFileById(d.getId());
4
+ console.log(e.getName());
@@ -0,0 +1,81 @@
1
+ import is from '@sindresorhus/is';
2
+ export const whichType = (element) => {
3
+ const ts = ["paragraph", "pageBreak", "textRun", "table", "tableRows", "tableCells", "content"]
4
+ const [t] = ts.filter(f => Reflect.has(element, f))
5
+ return t
6
+ }
7
+
8
+ export const report = (doc, what) => {
9
+ let newdoc = doc
10
+ let body = doc.body
11
+ let tabs = doc.tabs
12
+ // handle case where tabs is handling legacy doc.
13
+ if (!body) {
14
+ if (!tabs) {
15
+ console.log('missing tabs')
16
+ console.log(JSON.stringify(doc))
17
+ } else {
18
+ console.log('getting body from tabs')
19
+ newdoc = tabs[0].documentTab
20
+ body = newdoc.body
21
+ }
22
+ }
23
+
24
+ // drop the section break
25
+ const children = body.content.slice(1)
26
+ what += ` -children:${children.length}`
27
+ console.log(what)
28
+ let text = ' '
29
+ console.log(stringCircular(children))
30
+ const childProps = ["elements", "tableRows", "tableCells", "content", "listItem"]
31
+ const typer = (child, text, spaces = " ") => {
32
+ const type = whichType(child)
33
+ if (type) {
34
+ text += `\n${spaces}-${type} ${child.startIndex}:${child.endIndex}`
35
+ if (type === 'textRun') {
36
+ text += ` ${JSON.stringify(child[type].content)}`
37
+ }
38
+ const key = Reflect.ownKeys(child[type]).find(f => childProps.includes(f))
39
+ let arr = key && child[type][key]
40
+ if (!arr && is.array(child[type])) arr = child[type]
41
+
42
+ if (is.array(arr)) {
43
+ //text += spaces
44
+ arr.forEach(f => text = typer(f, text, spaces + " "))
45
+ //text += ''
46
+ }
47
+ }
48
+ return text
49
+ }
50
+ return children.map(f => typer(f, text)).join("\n")
51
+ }
52
+
53
+
54
+ export const scl = (doc) => {
55
+ if (!DocumentApp.isFake) {
56
+ const id = doc.getId()
57
+ doc.saveAndClose()
58
+ doc = DocumentApp.openById(id)
59
+ }
60
+ return doc
61
+ }
62
+
63
+ // The custom replacer function
64
+ const getCircularReplacer = () => {
65
+ const seen = new WeakSet(); // Use WeakSet to avoid memory leaks
66
+ return (_, value) => {
67
+ // If the value is an object and not null
68
+ if (typeof value === "object" && value !== null) {
69
+ // If we have already seen this object, it's a circular reference
70
+ if (seen.has(value)) {
71
+ return "[Circular]"; // Replace it with a placeholder
72
+ }
73
+ // If it's a new object, add it to our cache
74
+ seen.add(value);
75
+ }
76
+ // Return the value to be serialized
77
+ return value;
78
+ };
79
+ };
80
+
81
+ export const stringCircular = (ob) => JSON.stringify(ob, getCircularReplacer());
@@ -1,54 +1,26 @@
1
1
  import '../../main.js';
2
2
  import { moveToTempFolder, deleteTempFile } from '../tempfolder.js';
3
- import is from '@sindresorhus/is';
3
+ import { report, scl } from './dreport.js';
4
+
4
5
  const suffix = "-bruce"
5
6
 
6
- const whichType = (element) => {
7
- const ts = ["paragraph", "pageBreak", "textRun","table","tableRows","tableCells","content"]
8
- const [t] = ts.filter(f => Reflect.has(element, f))
9
- //if (!t) console.log('skipping element', element)
10
- return t
11
- }
7
+ const tabsa = () => {
12
8
 
13
- const report = (doc, what) => {
14
- const body = doc.body
15
- // drop the section break
16
- const children = body.content.slice(1)
17
- what += ` -children:${children.length}`
18
- console.log(what)
19
- let text = ' '
20
-
21
- const childProps = ["elements", "tableRows", "tableCells","content"]
22
- const typer = (child, text, spaces = " ") => {
23
- const type = whichType(child)
24
- if (type) {
25
- text += `\n${spaces}-${type} ${child.startIndex}:${child.endIndex}`
26
- if (type === 'textRun') {
27
- text += ` ${JSON.stringify(child[type].content)}`
28
- }
29
- const key = Reflect.ownKeys(child[type]).find (f=>childProps.includes(f))
30
- let arr = key && child[type][key]
31
- if (!arr && is.array (child[type])) arr = child[type]
32
-
33
- if (is.array(arr)) {
34
- //text += spaces
35
- arr.forEach(f => text = typer(f, text, spaces + " "))
36
- //text += ''
37
- }
38
- }
39
- return text
40
- }
41
- return children.map(f => typer(f, text)).join("\n")
42
- }
43
- const scl = (doc) => {
44
- if (!DocumentApp.isFake) {
45
- const id = doc.getId()
46
- doc.saveAndClose()
47
- doc = DocumentApp.openById(id)
48
- }
49
- return doc
9
+ let doc = DocumentApp.create("abc")
10
+ const id = doc.getId()
11
+ moveToTempFolder(id, suffix)
12
+
13
+ let body = doc.getBody()
14
+ console.log (body)
15
+ doc = scl(doc)
16
+ console.log(report(Docs.Documents.get(id, {includeTabsContent: true}), `\n1.empty document`))
17
+
18
+
19
+ deleteTempFile(id)
50
20
  }
51
21
 
22
+ //tabsa()
23
+
52
24
  // this is testing the new simplified method
53
25
  const pbnew = () => {
54
26
  let doc = DocumentApp.create("abc")
@@ -56,6 +28,16 @@ const pbnew = () => {
56
28
  moveToTempFolder(id, suffix)
57
29
  let body
58
30
 
31
+ body = doc.getBody()
32
+ body.appendListItem('l0a append')
33
+ doc = scl(doc)
34
+ console.log(report(Docs.Documents.get(id), `\nl0a.appended list item l0a`))
35
+
36
+ body = doc.getBody()
37
+ body.appendListItem('l01b append')
38
+ doc = scl(doc)
39
+ console.log(report(Docs.Documents.get(id), `\nl0a.appended list item l0b`))
40
+
59
41
  body = doc.getBody()
60
42
  body.appendTable([['']])
61
43
  doc = scl(doc)
@@ -66,6 +48,12 @@ const pbnew = () => {
66
48
  doc = scl(doc)
67
49
  console.log(report(Docs.Documents.get(id), `\n1.appended para`))
68
50
 
51
+ body = doc.getBody()
52
+ body.appendListItem('l1 append')
53
+ doc = scl(doc)
54
+ console.log(report(Docs.Documents.get(id), `\nl1.appended list item l1`))
55
+
56
+
69
57
  body = doc.getBody()
70
58
  body.insertParagraph(0, 'para 0')
71
59
  doc = scl(doc)
@@ -131,8 +119,8 @@ const pbnew = () => {
131
119
 
132
120
  deleteTempFile(id)
133
121
  }
134
- pbnew()
135
122
 
123
+ pbnew()
136
124
 
137
125
 
138
126
 
@@ -2,5 +2,11 @@ import "../../main.js";
2
2
  import { FakeSpreadsheetApp } from "../../src/services/spreadsheetapp/fakespreadsheetapp.js";
3
3
  import { moveToTempFolder, deleteTempFile } from "./tempfolder.js";
4
4
 
5
- const spreadsheet = SpreadsheetApp.openById("");
5
+ const spreadsheet = SpreadsheetApp.openById("###");
6
6
  const sheet = spreadsheet.getSheets()[0];
7
+ sheet.setName("sample");
8
+
9
+ const r = spreadsheet.getRangeByName("test111");
10
+ if (r) {
11
+ console.log(r.getA1Notation());
12
+ }
@@ -0,0 +1,59 @@
1
+ import "../../main.js";
2
+ import { FakeSpreadsheetApp } from "../../src/services/spreadsheetapp/fakespreadsheetapp.js";
3
+ import { moveToTempFolder, deleteTempFile } from "./tempfolder.js";
4
+
5
+ const spreadsheet = SpreadsheetApp.create("sample");
6
+ const sheet = spreadsheet.getSheets()[0];
7
+ const spreadsheetId = spreadsheet.getId();
8
+ moveToTempFolder(spreadsheetId);
9
+
10
+ // Create named ranges
11
+ const name1 = "sampleNamedRange1";
12
+ const range1 = sheet.getRange("A1:B2");
13
+ const name2 = "sampleNamedRange2";
14
+ const range2 = sheet.getRange("C1:E2");
15
+ spreadsheet.setNamedRange(name1, range1);
16
+ spreadsheet.setNamedRange(name2, range2);
17
+
18
+ // Get created named ranges from Spreadsheet
19
+ const res1 = spreadsheet
20
+ .getNamedRanges()
21
+ .map(
22
+ (rr) =>
23
+ `${rr.getName()}: '${rr.getRange().getSheet().getSheetName()}'!${rr
24
+ .getRange()
25
+ .getA1Notation()}`
26
+ );
27
+ console.log(res1);
28
+
29
+ // Get created named ranges from Sheet
30
+ const res2 = sheet
31
+ .getNamedRanges()
32
+ .map(
33
+ (rr) =>
34
+ `${rr.getName()}: '${rr.getRange().getSheet().getSheetName()}'!${rr
35
+ .getRange()
36
+ .getA1Notation()}`
37
+ );
38
+ console.log(res2);
39
+
40
+ // Update named ranges.
41
+ spreadsheet.getNamedRanges().forEach((r, i) => {
42
+ r.setName(`${r.getName()}_${i + 10}`);
43
+ const range = r.getRange().offset(0, 2);
44
+ r.setRange(range);
45
+ });
46
+ const res3 = spreadsheet
47
+ .getNamedRanges()
48
+ .map(
49
+ (rr) =>
50
+ `${rr.getName()}: '${rr.getRange().getSheet().getSheetName()}'!${rr
51
+ .getRange()
52
+ .getA1Notation()}`
53
+ );
54
+ console.log(res3);
55
+
56
+ // Remove named ranges
57
+ spreadsheet.getNamedRanges().forEach((r) => r.remove());
58
+
59
+ deleteTempFile(spreadsheetId);
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+
3
+ # A shell script to create a GitHub issue for the sandbox feature.
4
+
5
+ # Issue title
6
+ TITLE="Improve Sandbox Mode to fully emulate drive.file scope"
7
+
8
+ # Issue body in markdown
9
+ BODY=$(cat <<'EOF'
10
+ ## Summary
11
+
12
+ The current implementation of `gas-fakes` requires the full `https://www.googleapis.com/auth/drive` scope for initialization, primarily to access metadata about the root Drive folder. This is a stumbling block for users who want to run tests in a more restrictive environment using only the `drive.file` scope.
13
+
14
+ The `ScriptApp.__behavior.sandBoxMode` was introduced as a clever workaround to *emulate* the `drive.file` scope during test execution, but it doesn't solve the initial authorization requirement.
15
+
16
+ For more details on the current sandbox implementation, see sandbox.md.
17
+
18
+ ## TODO
19
+
20
+ We need to investigate ways to remove the dependency on root folder access during the `FakeDriveApp` initialization. This would allow `gas-fakes` to operate with only the `drive.file` scope, making the sandbox feature a true end-to-end emulation.
21
+
22
+ Possible areas to explore:
23
+ - Can we mock or bypass the root folder check during initialization if a specific `drive.file` mode is detected?
24
+ - Can we lazy-initialize parts of `FakeDriveApp` that require root access, and only fail if those specific methods (like `getRootFolder()`) are called without sufficient permissions?
25
+
26
+ This would be a significant improvement for security-conscious development and testing workflows.
27
+ EOF
28
+ )
29
+
30
+ # Create the issue using GitHub CLI
31
+ gh issue create --title "$TITLE" --body "$BODY" --label "enhancement" --label "help wanted"
package/package.json CHANGED
@@ -4,16 +4,17 @@
4
4
  },
5
5
  "dependencies": {
6
6
  "@mcpher/fake-gasenum": "^1.0.2",
7
+ "@mcpher/gas-fakes": "^1.0.14",
7
8
  "@mcpher/unit": "^1.1.11",
8
9
  "@sindresorhus/is": "^7.0.1",
9
10
  "archiver": "^7.0.1",
10
11
  "get-stream": "^9.0.1",
11
- "googleapis": "^150.0.1",
12
- "got": "^14.4.5",
12
+ "googleapis": "^157.0.0",
13
+ "got": "^14.4.7",
13
14
  "into-stream": "^8.0.1",
14
- "keyv": "^5.2.3",
15
- "keyv-file": "^5.1.1",
16
- "mime": "^4.0.6",
15
+ "keyv": "^5.5.0",
16
+ "keyv-file": "^5.1.3",
17
+ "mime": "^4.0.7",
17
18
  "sleep-synchronously": "^2.0.0",
18
19
  "subsume": "^4.0.0",
19
20
  "to-readable-stream": "^4.0.0",
@@ -45,10 +46,13 @@
45
46
  "testslides": "cp mainlocal.js main.js && node --env-file=.env ./test/testslides.js execute",
46
47
  "testdocsnext": "cp mainlocal.js main.js && node --env-file=.env ./test/testdocsnext.js execute",
47
48
  "testsheetstext": "cp mainlocal.js main.js && node --env-file=.env ./test/testsheetstext.js execute",
49
+ "testsheetsrange": "cp mainlocal.js main.js && node --env-file=.env ./test/testsheetsrange.js execute",
50
+ "testdocslistitems": "cp mainlocal.js main.js && node --env-file=.env ./test/testdocslistitems.js execute",
51
+ "testdocsall": "cp mainlocal.js main.js && node --env-file=.env ./test/testdocsall.js",
48
52
  "pub": "cp mainlocal.js main.js && npm publish --access public"
49
53
  },
50
54
  "name": "@mcpher/gas-fakes",
51
- "version": "1.0.14",
55
+ "version": "1.0.15",
52
56
  "license": "MIT",
53
57
  "main": "main.js",
54
58
  "description": "A proof of concept implementation of Apps Script Environment on Node",
@@ -1,11 +1,15 @@
1
1
  import { google } from "googleapis";
2
2
  import { Auth } from '../../support/auth.js'
3
+ import { syncLog} from '../../support/workersync/synclogger.js'
3
4
 
4
- export const getApiClient = (auth) => {
5
- return google.docs({ version: 'v1', auth });
6
- }
5
+ let __client = null;
7
6
 
8
- export const getAuthedClient = () => {
7
+ export const getDocsApiClient = () => {
9
8
  const auth = Auth.getAuth()
10
- return getApiClient(auth)
11
- }
9
+ if (!__client) {
10
+ syncLog('Creating new Docs API client');
11
+ __client = google.docs({ version: 'v1', auth });
12
+ }
13
+ return __client;
14
+ }
15
+
@@ -2,14 +2,12 @@
2
2
  * Advanced sheets service
3
3
  */
4
4
  import { Proxies } from '../../support/proxies.js'
5
- import { advClassMaker, notYetImplemented } from '../../support/helpers.js'
6
- import { getAuthedClient } from './docapis.js'
5
+ import { advClassMaker } from '../../support/helpers.js'
7
6
  import { newFakeAdvDocuments } from './fakeadvdocuments.js'
8
7
  import { docsCacher } from '../../support/docscacher.js';
9
8
 
10
9
  class FakeAdvDocs {
11
10
  constructor() {
12
- this.client = Proxies.guard(getAuthedClient())
13
11
  this.__fakeObjectType = "Docs"
14
12
 
15
13
  const propLists = {
@@ -9,6 +9,7 @@ import { Syncit } from '../../support/syncit.js'
9
9
  import is from '@sindresorhus/is'
10
10
  import { FakeAdvResource } from '../common/fakeadvresource.js';
11
11
 
12
+
12
13
  /**
13
14
  * the advanced docs Apps Script service faked - Documents class
14
15
  * @class FakeAdvDocuments
@@ -18,6 +19,19 @@ class FakeAdvDocuments extends FakeAdvResource {
18
19
  super(docs, 'documents', Syncit.fxDocs);
19
20
  this.__fakeObjectType = "Docs.Documents";
20
21
  }
22
+ // in sandbox mode only files created in this instance are
23
+ __allowed(id) {
24
+ if (!ScriptApp.__behavior.isAccessible(id)) {
25
+ throw new Error(`Access to document ${id} is not allowed in sandbox mode`);
26
+ }
27
+ return id
28
+ }
29
+ __addAllowed(id) {
30
+ if (ScriptApp.__behavior.sandBoxMode) {
31
+ ScriptApp.__behavior.addFile(id);
32
+ }
33
+ return id
34
+ }
21
35
 
22
36
  create(resource, options) {
23
37
  const { nargs, matchThrow } = signatureArgs(arguments, "Docs.Documents.create")
@@ -31,7 +45,7 @@ class FakeAdvDocuments extends FakeAdvResource {
31
45
 
32
46
  // maybe we need to throw an error
33
47
  ssError(response, "create")
34
-
48
+ this.__addAllowed(data.documentId);
35
49
  return data
36
50
  }
37
51
 
@@ -42,11 +56,12 @@ class FakeAdvDocuments extends FakeAdvResource {
42
56
  matchThrow("API call to docs.documents.batchUpdate failed with error: Invalid JSON payload received.");
43
57
  }
44
58
 
59
+
45
60
  // Invalidate the cache for this document since we are updating it.
46
61
  docsCacher.clear(documentId);
47
62
  // console.log (JSON.stringify(requests))
48
63
  const { response, data } = this._call("batchUpdate", {
49
- documentId: documentId,
64
+ documentId: this.__allowed(documentId),
50
65
  requestBody: requests
51
66
  });
52
67
 
@@ -87,7 +102,7 @@ class FakeAdvDocuments extends FakeAdvResource {
87
102
 
88
103
  if (is.nonEmptyObject(options) && !Reflect.ownKeys(options || {}).every(f => optionsSet.has(f))) matchThrow();
89
104
 
90
- const params = { documentId, ...(options || {}) };
105
+ const params = {documentId: this.__allowed(documentId), ...(options || {}) };
91
106
 
92
107
  const { response, data } = this._call("get", params);
93
108
  ssError(response, 'get');
@@ -1,11 +1,14 @@
1
1
  import { google } from "googleapis";
2
2
  import { Auth } from '../../support/auth.js'
3
+ import { syncLog} from '../../support/workersync/synclogger.js'
3
4
 
4
- export const getApiClient = (auth) => {
5
- return google.drive({ version: 'v3', auth });
6
- }
7
-
8
- export const getAuthedClient = () => {
5
+ let __client = null;
6
+ syncLog('...importing Drive API');
7
+ export const getDriveApiClient = () => {
9
8
  const auth = Auth.getAuth()
10
- return getApiClient(auth)
11
- }
9
+ if (!__client) {
10
+ syncLog('Creating new Drive API client');
11
+ __client = google.drive({ version: 'v3', auth });
12
+ }
13
+ return __client;
14
+ }
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import { Proxies } from '../../support/proxies.js'
5
5
  import { notYetImplemented } from '../../support/helpers.js'
6
- import { getAuthedClient } from './drapis.js'
7
6
  import { newFakeAdvDriveAbout } from './fakeadvdriveabout.js'
8
7
  import { newFakeAdvDriveFiles } from './fakeadvdrivefiles.js';
9
8
  import { newFakeAdvDriveApps } from './fakeadvdriveapps.js'
@@ -17,7 +16,6 @@ import { getDrivePerformance } from '../../support/filecache.js';
17
16
 
18
17
  class FakeAdvDrive {
19
18
  constructor() {
20
- this.client = Proxies.guard(getAuthedClient())
21
19
  this.__fakeObjectType = "Drive"
22
20
  }
23
21
  toString() {
@@ -71,6 +69,25 @@ class FakeAdvDrive {
71
69
  __getDrivePerformance() {
72
70
  return getDrivePerformance()
73
71
  }
72
+ // in sandbox mode only files created in this instance are
73
+ __allowed(id) {
74
+ // initially we will allow access to root here
75
+ // TODO - need to find a way to work without a root required in DriveApp
76
+ if (id === 'root') {
77
+ return id
78
+ }
79
+
80
+ if (!ScriptApp.__behavior.isAccessible(id)) {
81
+ throw new Error(`Access to file ${id} is not allowed in sandbox mode`);
82
+ }
83
+ return id
84
+ }
85
+ __addAllowed(id) {
86
+ if (ScriptApp.__behavior.sandBoxMode) {
87
+ ScriptApp.__behavior.addFile(id);
88
+ }
89
+ return id
90
+ }
74
91
 
75
92
  }
76
93
 
@@ -52,6 +52,7 @@ class FakeAdvDriveFiles {
52
52
  /// extract out the fieldslist
53
53
  const fields = params.fields.replace(/.*files\(([^)]*)\).*/,"$1")
54
54
 
55
+ /// TODO - filter out any files we wouldn't be allowed to see in sandbox mode
55
56
  data.files.forEach(f => {
56
57
  improveFileCache(f.id, f,fields)
57
58
  })
@@ -114,7 +115,7 @@ class FakeAdvDriveFiles {
114
115
  * @returns {Drive.File}
115
116
  */
116
117
  get(id, params = {}, { allow404 = true } = {}) {
117
- const {data} = Syncit.fxDriveGet ({ id, params, allow404, allowCache: true })
118
+ const {data} = Syncit.fxDriveGet ({ id: this.drive.__allowed(id), prop: apiProp, method: 'get', params, allow404, allowCache: true })
118
119
  return data
119
120
  }
120
121
 
@@ -132,7 +133,9 @@ class FakeAdvDriveFiles {
132
133
 
133
134
  // must have some kind of name so derive if not given
134
135
  const name = file.name || blob?.getName() || (isFolder(file) ? "New Folder" : "Untitled")
135
- return updateOrCreate ({method: "create", file: { ...file, name }, blob, fields, params })
136
+ const d = updateOrCreate ({method: "create", file: { ...file, name }, blob, fields, params })
137
+ this.drive.__addAllowed(d.id);
138
+ return d
136
139
 
137
140
  }
138
141
 
@@ -153,7 +156,7 @@ class FakeAdvDriveFiles {
153
156
  if (!is.nonEmptyString(fileId)) {
154
157
  throw new Error(`API call to drive.files.update failed with error: Required`)
155
158
  }
156
- return updateOrCreate ({method: 'update', file, blob, fileId , fields, params})
159
+ return updateOrCreate ({method: 'update', file, blob, fileId: this.drive.__allowed(fileId) , fields, params})
157
160
  }
158
161
  /**
159
162
  * ceate a file and optionally upload some data
@@ -169,13 +172,13 @@ class FakeAdvDriveFiles {
169
172
  const fields = mergeParamStrings(options.fields || "",minFields)
170
173
  const params = {
171
174
  fields,
172
- fileId,
175
+ fileId: this.drive.__allowed(fileId),
173
176
  resource: file
174
177
  }
175
178
 
176
179
  const { response, data } = Syncit.fxDrive({ prop: apiProp, method: 'copy', params, options })
177
180
  checkResponse(data?.id, response, false)
178
- improveFileCache(data.id, data,fields)
181
+ improveFileCache(this.drive.__addAllowed(data.id), data,fields)
179
182
  return data
180
183
 
181
184
  }
@@ -8,7 +8,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
8
8
  // The service name is 'permissions', and it uses the main Drive sync method.
9
9
  super(drive, 'permissions', Syncit.fxDrive);
10
10
  this.__fakeObjectType = "Drive.Permissions";
11
-
11
+ this.drive = drive;
12
12
  const props = ['get', 'update'];
13
13
  props.forEach(f => {
14
14
  if (!this[f]) {
@@ -23,7 +23,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
23
23
 
24
24
  const params = {
25
25
  resource,
26
- fileId,
26
+ fileId: this.drive.__allowed(fileId),
27
27
  ...(optionalArgs || {})
28
28
  };
29
29
  const { data } = this._call('create', params);
@@ -35,7 +35,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
35
35
  if (nargs < 2 || nargs > 3) matchThrow();
36
36
 
37
37
  const params = {
38
- fileId,
38
+ fileId: this.drive.__allowed(fileId),
39
39
  permissionId,
40
40
  ...(optionalArgs || {})
41
41
  };
@@ -47,7 +47,7 @@ class FakeAdvDrivePermissions extends FakeAdvResource {
47
47
  const { nargs, matchThrow } = signatureArgs(arguments, "Drive.Permissions.list");
48
48
  if (nargs < 1 || nargs > 2) matchThrow();
49
49
 
50
- const params = { fileId, ...(optionalArgs || {}) };
50
+ const params = { fileId: this.drive.__allowed(fileId), ...(optionalArgs || {}) };
51
51
  const { data } = this._call('list', params);
52
52
  return data;
53
53
  }
@@ -3,15 +3,13 @@
3
3
  */
4
4
  import { Proxies } from '../../support/proxies.js'
5
5
  import { advClassMaker } from '../../support/helpers.js'
6
- import { getAuthedClient } from './shapis.js'
7
6
  import { newFakeAdvSheetsSpreadsheets } from './fakeadvsheetsspreadsheets.js'
8
7
  import { sheetsCacher } from '../../support/sheetscacher.js';
9
- import { sheets } from 'googleapis/build/src/apis/sheets/index.js';
8
+
10
9
 
11
10
  class FakeAdvSheets {
12
11
  constructor() {
13
- this.client = Proxies.guard(getAuthedClient())
14
- this.__fakeObjectType = "Docs"
12
+ this.__fakeObjectType = "Sheets"
15
13
 
16
14
  const propLists = {
17
15
  newGridRange: ['sheetId', 'startRowIndex', 'startColumnIndex', 'endRowIndex', 'endColumnIndex'],
@@ -880,7 +878,19 @@ class FakeAdvSheets {
880
878
  __getSheetsPerformance() {
881
879
  return sheetsCacher.getPerformance()
882
880
  }
881
+ // in sandbox mode only files created in this instance are
882
+ __allowed(id) {
883
+ if (!ScriptApp.__behavior.isAccessible(id)) {
884
+ throw new Error(`Access to document ${id} is not allowed in sandbox mode`);
885
+ }
886
+ return id
887
+ }
888
+ __addAllowed(id) {
889
+ if (ScriptApp.__behavior.sandBoxMode) {
890
+ ScriptApp.__behavior.addFile(id);
891
+ }
892
+ return id
893
+ }
883
894
  }
884
895
 
885
896
  export const newFakeAdvSheets = (...args) => Proxies.guard(new FakeAdvSheets(...args))
886
-