@mcpher/gas-fakes 1.0.0
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/LICENSE +21 -0
- package/README.md +305 -0
- package/main.js +1 -0
- package/package.json +23 -0
- package/src/index.js +4 -0
- package/src/services/drive/app.js +31 -0
- package/src/services/drive/drapis.js +23 -0
- package/src/services/drive/fakedrive.js +568 -0
- package/src/services/drive/fakedrivehelpers.js +141 -0
- package/src/services/scriptapp/app.js +133 -0
- package/src/services/urlfetchapp/app.js +128 -0
- package/src/services/utilities/app.js +37 -0
- package/src/services/utilities/fakeblob.js +73 -0
- package/src/support/auth.js +154 -0
- package/src/support/constants.js +16 -0
- package/src/support/peeker.js +44 -0
- package/src/support/proxies.js +74 -0
- package/src/support/syncit.js +235 -0
- package/src/support/utils.js +144 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// fake script app to get oauth token from application default credentials on Apps Script
|
|
2
|
+
// first set up and test ADC with required scopes - see https://ramblings.mcpher.com/application-default-credentials-with-google-cloud-and-workspace-apis
|
|
3
|
+
// Note that all async type functions have been converted to synch to make it Apps Script like
|
|
4
|
+
|
|
5
|
+
import { Syncit } from '../../support/syncit.js'
|
|
6
|
+
import { Auth } from '../../support/auth.js'
|
|
7
|
+
import { Proxies } from '../../support/proxies.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* fake ScriptApp.getOAuthToken
|
|
11
|
+
* @return {string} token
|
|
12
|
+
*/
|
|
13
|
+
const getOAuthToken = () => {
|
|
14
|
+
// make a sync request in a subprocess to get an access token
|
|
15
|
+
return Syncit.fxGetAccessToken()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const limitMode = (mode) => {
|
|
19
|
+
if (mode !== ScriptApp.AuthMode.FULL) {
|
|
20
|
+
throw new Error(`only ${ScriptApp.AuthMode.FULL} is supported as mode for now`)
|
|
21
|
+
}
|
|
22
|
+
// the scopes from the manifest should have been set
|
|
23
|
+
if (!Auth.hasAuth()) {
|
|
24
|
+
throw new Error(`manifest hasnt been initialized`)
|
|
25
|
+
}
|
|
26
|
+
return mode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* these have been converted with a sync version
|
|
31
|
+
* @param {ScriptApp.AuthMode} mode mode to check
|
|
32
|
+
* @returns null
|
|
33
|
+
*/
|
|
34
|
+
const requireAllScopes = (mode) => {
|
|
35
|
+
limitMode(mode)
|
|
36
|
+
return checkScopesMatch(Array.from(Auth.getAuthedScopes().keys()))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* these have been converted with a sync version
|
|
41
|
+
* see https://developers.google.com/apps-script/reference/script/script-app#requireScopes(AuthMode,String)
|
|
42
|
+
* @param {ScriptApp.AuthMode} mode mode to check
|
|
43
|
+
* @param {string[]} required scopes required
|
|
44
|
+
* @returns null
|
|
45
|
+
*/
|
|
46
|
+
const requireScopes = (mode, required) => {
|
|
47
|
+
// only supporting FULL for now
|
|
48
|
+
limitMode(mode)
|
|
49
|
+
return checkScopesMatch(required)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* a sync version of token checking
|
|
54
|
+
* @param {string} token the token to check
|
|
55
|
+
* @returns {object} access token info
|
|
56
|
+
*/
|
|
57
|
+
const checkToken = (accessToken) => {
|
|
58
|
+
return Syncit.fxCheckToken(accessToken)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* check that all scopes requested have been asked for
|
|
63
|
+
* @param {string[]} required
|
|
64
|
+
* @returns null
|
|
65
|
+
*/
|
|
66
|
+
const checkScopesMatch = (required) => {
|
|
67
|
+
|
|
68
|
+
// we can do a sync version of the accesstoken fetch
|
|
69
|
+
const token = getOAuthToken()
|
|
70
|
+
const tokenInfo = checkToken(token)
|
|
71
|
+
|
|
72
|
+
// now we're syncronous all the way
|
|
73
|
+
const tokened = new Set(tokenInfo.scope.split(" "))
|
|
74
|
+
|
|
75
|
+
// see which ones are missing
|
|
76
|
+
const missing = required.filter(s => {
|
|
77
|
+
// setting this scope causes gcloud to block - but we dot need it anywat as the default ADC allow it, so we have to skip it
|
|
78
|
+
const ignore = "https://www.googleapis.com/auth/script.external_request"
|
|
79
|
+
// if drive is authorized and drive.readonly is required that's okay too
|
|
80
|
+
// if drive.readonly is authorized and drive is requested thats not
|
|
81
|
+
return !(s === ignore || tokened.has(s.replace(/\.readonly$/, "")))
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
if (missing.length) {
|
|
85
|
+
throw new Error(`These scopes are required but have not been authorized ${missing.join(",")}`)
|
|
86
|
+
}
|
|
87
|
+
return null
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// This will eventually hold a proxy for ScriptApp
|
|
92
|
+
let _app = null
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* adds to global space to mimic Apps Script behavior
|
|
97
|
+
*/
|
|
98
|
+
const name = "ScriptApp"
|
|
99
|
+
|
|
100
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
101
|
+
|
|
102
|
+
console.log ('setting script app to global')
|
|
103
|
+
|
|
104
|
+
const getApp = () => {
|
|
105
|
+
|
|
106
|
+
// if it hasn't been intialized yet then do that
|
|
107
|
+
if (!_app) {
|
|
108
|
+
|
|
109
|
+
// we also need to do the manifest scopes thing and the project id
|
|
110
|
+
const projectId = Syncit.fxGetProjectId()
|
|
111
|
+
const manifest = Syncit.fxGetManifest()
|
|
112
|
+
Auth.setProjectId (projectId)
|
|
113
|
+
Auth.setManifestScopes(manifest)
|
|
114
|
+
|
|
115
|
+
_app = {
|
|
116
|
+
getOAuthToken,
|
|
117
|
+
requireAllScopes,
|
|
118
|
+
requireScopes,
|
|
119
|
+
AuthMode: {
|
|
120
|
+
FULL: 'FULL'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
// this is the actual driveApp we'll return from the proxy
|
|
127
|
+
return _app
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
Proxies.registerProxy(name, getApp)
|
|
132
|
+
|
|
133
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// fake Apps Script UrlFetchApp
|
|
2
|
+
|
|
3
|
+
import { Auth } from '../../support/auth.js'
|
|
4
|
+
import { Syncit } from '../../support/syncit.js'
|
|
5
|
+
import { Proxies } from '../../support/proxies.js'
|
|
6
|
+
// Note that all async type functions have been converted to synch ro make it Apps Script like
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* make got response look like UrlFetchApp response
|
|
11
|
+
* @param {Response} reponse
|
|
12
|
+
* @return {FakeHTTPResponse} UrlFetchApp flavor
|
|
13
|
+
*/
|
|
14
|
+
const responsify = (response) => {
|
|
15
|
+
|
|
16
|
+
// TODO test all these
|
|
17
|
+
// getAllHeaders() Object Returns an attribute/value map of headers for the HTTP response, with headers that have multiple values returned as arrays.
|
|
18
|
+
// need to identify the difference between this and getHeaders
|
|
19
|
+
const getAllHeaders = () => response.rawHeaders
|
|
20
|
+
|
|
21
|
+
// getResponseCode() Integer Get the HTTP status code (200 for OK, etc.) of an HTTP response
|
|
22
|
+
const getResponseCode = () => response.statusCode
|
|
23
|
+
|
|
24
|
+
// getContentText() String Gets the content of an HTTP response encoded as a string.
|
|
25
|
+
|
|
26
|
+
const getContentText = () => response.body
|
|
27
|
+
|
|
28
|
+
// getHeaders() Object Returns an attribute/value map of headers for the HTTP response.
|
|
29
|
+
const getHeaders = () => response.headers
|
|
30
|
+
|
|
31
|
+
/* TODO
|
|
32
|
+
getAs(contentType) Blob Return the data inside this object as a blob converted to the specified content type.
|
|
33
|
+
getBlob() Blob Return the data inside this object as a blob.
|
|
34
|
+
getContent() Byte[] Gets the raw binary content of an HTTP response.
|
|
35
|
+
getContentText(charset) String Returns the content of an HTTP response encoded as a string of the given charset.
|
|
36
|
+
*/
|
|
37
|
+
return {
|
|
38
|
+
getAllHeaders,
|
|
39
|
+
getResponseCode,
|
|
40
|
+
getContentText,
|
|
41
|
+
getHeaders
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// this has been syncified
|
|
46
|
+
const fetch = (url, options = {}) => {
|
|
47
|
+
|
|
48
|
+
// check options for method and provide default
|
|
49
|
+
options.method = options.method || "get"
|
|
50
|
+
options = Auth.googify(options)
|
|
51
|
+
|
|
52
|
+
const responseFields = [
|
|
53
|
+
'rawHeaders',
|
|
54
|
+
'statusCode',
|
|
55
|
+
'body',
|
|
56
|
+
'headers'
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
const response = Syncit.fxFetch(url, options, responseFields)
|
|
60
|
+
return responsify(response)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// This will eventually hold a proxy for DriveApp
|
|
65
|
+
let _app = null
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* adds to global space to mimic Apps Script behavior
|
|
69
|
+
*/
|
|
70
|
+
const name = "UrlFetchApp"
|
|
71
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
72
|
+
|
|
73
|
+
const getApp = () => {
|
|
74
|
+
// if it hasne been intialized yet then do that
|
|
75
|
+
if (!_app) {
|
|
76
|
+
_app = {
|
|
77
|
+
fetch
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// this is the actual driveApp we'll return from the proxy
|
|
81
|
+
return _app
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Proxies.registerProxy (name, getApp)
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/** got reponse props
|
|
90
|
+
[ '_events', 'object' ],
|
|
91
|
+
[ '_readableState', 'object' ],
|
|
92
|
+
[ '_writableState', 'object' ],
|
|
93
|
+
[ 'allowHalfOpen', 'boolean' ],
|
|
94
|
+
[ '_destroy', 'function' ],
|
|
95
|
+
[ '_maxListeners', 'undefined' ],
|
|
96
|
+
[ '_eventsCount', 'number' ],
|
|
97
|
+
[ 'socket', 'object' ],
|
|
98
|
+
[ 'httpVersionMajor', 'number' ],
|
|
99
|
+
[ 'httpVersionMinor', 'number' ],
|
|
100
|
+
[ 'httpVersion', 'string' ],
|
|
101
|
+
[ 'complete', 'boolean' ],
|
|
102
|
+
[ 'rawHeaders', 'object' ],
|
|
103
|
+
[ 'rawTrailers', 'object' ],
|
|
104
|
+
[ 'joinDuplicateHeaders', 'undefined' ],
|
|
105
|
+
[ 'aborted', 'boolean' ],
|
|
106
|
+
[ 'upgrade', 'boolean' ],
|
|
107
|
+
[ 'url', 'string' ],
|
|
108
|
+
[ 'method', 'object' ],
|
|
109
|
+
[ 'statusCode', 'number' ],
|
|
110
|
+
[ 'statusMessage', 'string' ],
|
|
111
|
+
[ 'client', 'object' ],
|
|
112
|
+
[ '_consuming', 'boolean' ],
|
|
113
|
+
[ '_dumped', 'boolean' ],
|
|
114
|
+
[ 'req', 'object' ],
|
|
115
|
+
[ 'timings', 'object' ],
|
|
116
|
+
[ 'headers', 'object' ],
|
|
117
|
+
[ 'setTimeout', 'function' ],
|
|
118
|
+
[ 'trailers', 'object' ],
|
|
119
|
+
[ 'requestUrl', 'object' ],
|
|
120
|
+
[ 'redirectUrls', 'object' ],
|
|
121
|
+
[ 'request', 'object' ],
|
|
122
|
+
[ 'isFromCache', 'boolean' ],
|
|
123
|
+
[ 'ip', 'string' ],
|
|
124
|
+
[ 'retryCount', 'number' ],
|
|
125
|
+
[ 'ok', 'boolean' ],
|
|
126
|
+
[ 'rawBody', 'object' ],
|
|
127
|
+
[ 'body', 'string' ]
|
|
128
|
+
*/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import sleepSynchronously from 'sleep-synchronously';
|
|
2
|
+
import { Utils } from '../../support/utils.js'
|
|
3
|
+
import { Proxies } from '../../support/proxies.js'
|
|
4
|
+
import { newBlob } from './fakeblob.js'
|
|
5
|
+
/**
|
|
6
|
+
* a blocking sleep to emulate Apps Script
|
|
7
|
+
* @param {number} ms number of milliseconds to sleep
|
|
8
|
+
*/
|
|
9
|
+
const sleep = (ms) => {
|
|
10
|
+
sleepSynchronously(Utils.assertType (ms, 'number',`Cannot convert ${ms} to int.`));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// This will eventually hold a proxy for DriveApp
|
|
15
|
+
let _app = null
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* adds to global space to mimic Apps Script behavior
|
|
19
|
+
*/
|
|
20
|
+
const name = "Utilities"
|
|
21
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
22
|
+
|
|
23
|
+
const getApp = () => {
|
|
24
|
+
// if it hasne been intialized yet then do that
|
|
25
|
+
if (!_app) {
|
|
26
|
+
_app = {
|
|
27
|
+
sleep,
|
|
28
|
+
newBlob
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// this is the actual driveApp we'll return from the proxy
|
|
32
|
+
return _app
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Proxies.registerProxy (name, getApp)
|
|
36
|
+
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js'
|
|
2
|
+
import { Utils } from '../../support/utils.js'
|
|
3
|
+
import { isGoogleType } from '../../support/constants.js'
|
|
4
|
+
|
|
5
|
+
import mime from 'mime';
|
|
6
|
+
// Apps Script blob fake
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FakeBlob {
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @constructor
|
|
13
|
+
* @param {byte[]} [data] data
|
|
14
|
+
* @param {string} [contentType]
|
|
15
|
+
* @param {string} [name]
|
|
16
|
+
* @returns {FakeDriveFile}
|
|
17
|
+
*/
|
|
18
|
+
constructor(data, contentType, name) {
|
|
19
|
+
this._data = Utils.settleAsBytes(data)
|
|
20
|
+
this._contentType = contentType ||
|
|
21
|
+
(Utils.isString(data) ? 'text/plain' : null)
|
|
22
|
+
this._name = name || null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
getBytes() {
|
|
27
|
+
return this._data
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getContentType() {
|
|
31
|
+
return this._contentType
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getName() {
|
|
35
|
+
return this._name
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isGoogleType() {
|
|
39
|
+
return isGoogleType(this.getContentType())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getDataAsString(charset) {
|
|
43
|
+
return Utils.bytesToString(this._data, charset)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
copyBlob() {
|
|
47
|
+
return newBlob(this.getBytes(), this.getContentType(), this.getName())
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setBytes(data) {
|
|
51
|
+
this._data = Utils.assertType(data, 'array')
|
|
52
|
+
return this
|
|
53
|
+
}
|
|
54
|
+
setContentType(contentType) {
|
|
55
|
+
this._contentType = contentType
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setContentTypeFromExtension() {
|
|
60
|
+
return this.setContentType(mime.getType(this.getName()))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setDataFromString(string, charset) {
|
|
64
|
+
return this.setBytes(Utils.stringToBytes(string, charset))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setName(name) {
|
|
68
|
+
this._name = name
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
export const newBlob = (...args) => Proxies.guard(new FakeBlob(...args))
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { GoogleAuth } from 'google-auth-library'
|
|
2
|
+
import {readFile} from 'node:fs/promises'
|
|
3
|
+
import got from 'got'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import {Utils} from './utils.js'
|
|
6
|
+
|
|
7
|
+
const _authScopes = new Set([])
|
|
8
|
+
let _auth = null
|
|
9
|
+
let _projectId = null
|
|
10
|
+
|
|
11
|
+
const setProjectId = (projectId) => _projectId = projectId
|
|
12
|
+
const setManifestScopes = (manifest) => setAuth(Utils.arrify(manifest.oauthScopes))
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* get the manifest scopes and set them
|
|
16
|
+
* @param {string} [manifestParh] the manifest file path
|
|
17
|
+
* @returns {Promise <string[]>} the scopes
|
|
18
|
+
*/
|
|
19
|
+
const initManifestScopes = async (manifestParh) => {
|
|
20
|
+
const scopes = await getManifestScopes(manifestParh)
|
|
21
|
+
setAuth (scopes)
|
|
22
|
+
// we also set the project id to avoid catching that every time
|
|
23
|
+
// the project id is required for fetches to workspace apis using application default credentials
|
|
24
|
+
_projectId = await authProjectId()
|
|
25
|
+
return scopes
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* get the manifest content and parse
|
|
29
|
+
* @param {string} [manifestPath] the manifest file path
|
|
30
|
+
* @returns {Promise <object>} the manifest
|
|
31
|
+
*/
|
|
32
|
+
const getManifest = async (manifestPath='./appsscript.json') => {
|
|
33
|
+
const mainDir = path.dirname(process.argv[1])
|
|
34
|
+
const manifestFile = path.resolve ( mainDir, manifestPath)
|
|
35
|
+
console.log (`using manifest file:${manifestFile}`)
|
|
36
|
+
const contents = await readFile(manifestFile, { encoding: 'utf8' })
|
|
37
|
+
return JSON.parse (contents)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* get the scopes from the manifest
|
|
42
|
+
* @param {string} [path] the manifest file path
|
|
43
|
+
* @returns {Promise <string[]>} the scopes required by the manifest
|
|
44
|
+
*/
|
|
45
|
+
const getManifestScopes = async (path) => {
|
|
46
|
+
const manifest = await getManifest(path)
|
|
47
|
+
return Utils.arrify(manifest.oauthScopes)
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* we'll be using adc credentials so no need for any special auth here
|
|
51
|
+
* the idea here is to keep addign scopes to any auth so we have them all
|
|
52
|
+
* @param {string[]} [scopes=[]] the required scopes will be added to existing scopes already asked for
|
|
53
|
+
* @returns {GoogleAuth.auth}
|
|
54
|
+
*/
|
|
55
|
+
const setAuth = (scopes = []) => {
|
|
56
|
+
|
|
57
|
+
if (!hasAuth() || !scopes.every(s => _authScopes.has(s))) {
|
|
58
|
+
_auth = new GoogleAuth({
|
|
59
|
+
scopes
|
|
60
|
+
})
|
|
61
|
+
scopes.forEach(s => _authScopes.add(s))
|
|
62
|
+
}
|
|
63
|
+
return getAuth()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* if we're doing a fetch on drive API we need a special header
|
|
69
|
+
*/
|
|
70
|
+
const googify = (options = {}) => {
|
|
71
|
+
const { headers } = options
|
|
72
|
+
|
|
73
|
+
// no auth, therefore no need
|
|
74
|
+
if (!headers || !hasAuth()) return options
|
|
75
|
+
|
|
76
|
+
// if no authorization, we dont need this either
|
|
77
|
+
if (!Reflect.has(headers, "Authorization")) return options
|
|
78
|
+
|
|
79
|
+
// we'll need the projectID for this
|
|
80
|
+
// note - you must add the x-goog-user-project header, otherwise it'll use some nonexistent project
|
|
81
|
+
// see https://cloud.google.com/docs/authentication/rest#set-billing-project
|
|
82
|
+
// this has been syncified
|
|
83
|
+
const projectId = getProjectId()
|
|
84
|
+
return {
|
|
85
|
+
...options,
|
|
86
|
+
headers: {
|
|
87
|
+
"x-goog-user-project": projectId,
|
|
88
|
+
...headers
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @returns {Promise <string>} the projectId
|
|
95
|
+
*/
|
|
96
|
+
const authProjectId = async () => {
|
|
97
|
+
return getAuth().getProjectId()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* this would have been set up when manifest was imported
|
|
102
|
+
* @returns {string} the project id
|
|
103
|
+
*/
|
|
104
|
+
const getProjectId = () => {
|
|
105
|
+
if (Utils.isNU(_projectId)) {
|
|
106
|
+
throw new Error ('Project id not set - did you forget to run initManifestScopes?')
|
|
107
|
+
}
|
|
108
|
+
return _projectId
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @returns {Boolean} checks to see if auth has bee initialized yet
|
|
113
|
+
*/
|
|
114
|
+
const hasAuth = () => Boolean (_auth)
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @returns {GoogleAuth.auth}
|
|
118
|
+
*/
|
|
119
|
+
const getAuth = () => {
|
|
120
|
+
if (!hasAuth()) throw new Error(`auth hasnt been intialized with setAuth yet`)
|
|
121
|
+
return _auth
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* gets the info about an access token
|
|
126
|
+
* @param {string} accessToken the accessToken to check
|
|
127
|
+
* @returns {Promise <object>} access toekn info
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
const checkToken = async (accessToken) => {
|
|
131
|
+
const pack = await got(`https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${accessToken}`).json()
|
|
132
|
+
return pack
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* these are the ones that have been so far requested
|
|
137
|
+
* @returns {Set}
|
|
138
|
+
*/
|
|
139
|
+
const getAuthedScopes = () => _authScopes
|
|
140
|
+
|
|
141
|
+
export const Auth = {
|
|
142
|
+
checkToken,
|
|
143
|
+
getAuth,
|
|
144
|
+
hasAuth,
|
|
145
|
+
getProjectId,
|
|
146
|
+
setAuth,
|
|
147
|
+
getManifestScopes,
|
|
148
|
+
getManifest,
|
|
149
|
+
initManifestScopes,
|
|
150
|
+
getAuthedScopes,
|
|
151
|
+
googify,
|
|
152
|
+
setProjectId,
|
|
153
|
+
setManifestScopes
|
|
154
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @constant
|
|
3
|
+
* @type {string}
|
|
4
|
+
* @default
|
|
5
|
+
*/
|
|
6
|
+
export const gooType = "application/vnd.google-apps"
|
|
7
|
+
/**
|
|
8
|
+
* mimetype of a folder
|
|
9
|
+
* @constant
|
|
10
|
+
* @type {string}
|
|
11
|
+
* @default
|
|
12
|
+
*/
|
|
13
|
+
export const folderType = `${gooType}.folder`
|
|
14
|
+
|
|
15
|
+
export const isGoogleType = (mimeType) =>
|
|
16
|
+
mimeType && mimeType.substring(0,gooType.length) === gooType
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Proxies } from './proxies.js'
|
|
2
|
+
/**
|
|
3
|
+
* this is a class to add a hasnext to a generator
|
|
4
|
+
* @class Peeker
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
class Peeker {
|
|
8
|
+
/**
|
|
9
|
+
* @constructor
|
|
10
|
+
* @param {function} generator the generator function to add a hasNext() to
|
|
11
|
+
* @returns {Peeker}
|
|
12
|
+
*/
|
|
13
|
+
constructor(generator) {
|
|
14
|
+
this.generator = generator
|
|
15
|
+
// in order to be able to do a hasnext we have to actually get the value
|
|
16
|
+
// this is the next value stored
|
|
17
|
+
this.peeked = generator.next()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* we see if there's a next if the peeked at is all over
|
|
22
|
+
* @returns {Boolean}
|
|
23
|
+
*/
|
|
24
|
+
hasNext () {
|
|
25
|
+
return !this.peeked.done
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* get the next value - actually its already got and storef in peeked
|
|
30
|
+
* @returns {object} {value, done}
|
|
31
|
+
*/
|
|
32
|
+
next () {
|
|
33
|
+
if (!this.hasNext()) {
|
|
34
|
+
// TODO find out what driveapp does
|
|
35
|
+
throw new Error ('iterator is exhausted - there is no more')
|
|
36
|
+
}
|
|
37
|
+
// instead of returning the next, we return the prepeeked next
|
|
38
|
+
const value = this.peeked.value
|
|
39
|
+
this.peeked = this.generator.next()
|
|
40
|
+
return value
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const newPeeker = (...args) => Proxies.guard(new Peeker (...args))
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* diverts the property get to another object returned by the getApp function
|
|
5
|
+
* @param {function} a function to get the proxy object to substitutes
|
|
6
|
+
* @returns {function} a handler for a proxy
|
|
7
|
+
*/
|
|
8
|
+
const getAppHandler = (getApp) => {
|
|
9
|
+
return {
|
|
10
|
+
|
|
11
|
+
get(_, prop, receiver) {
|
|
12
|
+
// this will let the caller know we're not really running in Apps Script
|
|
13
|
+
return (prop === 'isFake') ? true : Reflect.get(getApp(), prop, receiver);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
ownKeys(_) {
|
|
17
|
+
return Reflect.ownKeys(getApp())
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const registerProxy = (name, getApp) => {
|
|
23
|
+
const value = new Proxy({}, getAppHandler(getApp))
|
|
24
|
+
// add it to the global space to mimic what apps script does
|
|
25
|
+
Object.defineProperty(globalThis, name, {
|
|
26
|
+
value,
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: false,
|
|
29
|
+
writable: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* for validating attempts to access non existent properties
|
|
38
|
+
*/
|
|
39
|
+
const validateProperties = () => {
|
|
40
|
+
return {
|
|
41
|
+
get(target, prop, receiver) {
|
|
42
|
+
if (
|
|
43
|
+
// skip any inserted symbos
|
|
44
|
+
typeof prop !== 'symbol' &&
|
|
45
|
+
// sometimes typeof & console.log looks for ths
|
|
46
|
+
prop !== 'inspect' &&
|
|
47
|
+
// this is a mysterious property that APPS script sometimes checks for
|
|
48
|
+
prop !== '__GS_INTERNAL_isProxy' &&
|
|
49
|
+
// check the object has this property
|
|
50
|
+
!Reflect.has(target, prop)
|
|
51
|
+
)
|
|
52
|
+
throw new Error(`attempt to get non-existent property ${prop}: may not be implemented yet`)
|
|
53
|
+
|
|
54
|
+
return Reflect.get(target, prop, receiver);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
set(target, prop, value, receiver) {
|
|
58
|
+
if (!Reflect.has(target, prop))
|
|
59
|
+
throw `guard attempt to set non-existent property ${prop}`;
|
|
60
|
+
return Reflect.set(target, prop, value, receiver);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// used to trap access to unknown properties
|
|
66
|
+
const guard = (target) => {
|
|
67
|
+
return new Proxy(target, validateProperties);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const Proxies = {
|
|
71
|
+
getAppHandler,
|
|
72
|
+
registerProxy,
|
|
73
|
+
guard
|
|
74
|
+
}
|