gas-client
Advanced tools
Comparing version 0.3.0 to 1.0.0-pr.1
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports["default"] = void 0; | ||
var _uuid = require("uuid"); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
/** | ||
* Util that returns true if allowedDevelopmentDomains matches origin | ||
* @param {string|function} allowedDevelopmentDomains either a string of space-separated allowed subdomains or a function that accepts the origin as an argument and returns true if permitted | ||
* @param {string} origin the target origin subdomain to compare against | ||
*/ | ||
var checkAllowList = function checkAllowList(allowedDevelopmentDomains, origin) { | ||
if (typeof allowedDevelopmentDomains === 'string') { | ||
return allowedDevelopmentDomains.split(' ').some(function (permittedOrigin) { | ||
return permittedOrigin === origin; | ||
}); | ||
} | ||
if (typeof allowedDevelopmentDomains === 'function') { | ||
return allowedDevelopmentDomains(origin) === true; | ||
} | ||
return false; | ||
}; | ||
var Server = | ||
/** | ||
* Accepts a single `config` object | ||
* @param {object} [config] An optional config object for use in development. | ||
* @param {string|function} [config.allowedDevelopmentDomains] An optional config to specify which domains are permitted for receiving communication from a parent window. This is a security setting, and if not specified, will block functionality in development. Will accept either a space-separated string of allowed subdomains, e.g. `https://localhost:3000 http://localhost:3000` (notice no trailing slash); or a function that takes in the requesting origin should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin)` | ||
* @param {string} [config.parentTargetOrigin] An optional config to specify which parent window domain this client can send communication to. Defaults to own domain for backward compatibility with Google Apps Script Webpack Dev Server development tool (domain where the client is running, e.g. localhost). Can be '*' to allow all parent domains. | ||
*/ | ||
function Server() { | ||
var _this = this; | ||
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
_classCallCheck(this, Server); | ||
// skip the reserved names: https://developers.google.com/apps-script/guides/html/reference/run | ||
var ignoredFunctionNames = ['withFailureHandler', 'withLogger', 'withSuccessHandler', 'withUserObject']; | ||
this.serverFunctions = {}; | ||
try { | ||
// get the names of all of our publicly accessible server functions | ||
var functionNames = Object.keys(google.script.run).filter( // filter out the reserved names -- we don't want those | ||
function (functionName) { | ||
return !ignoredFunctionNames.includes(functionName); | ||
}); // attach Promise-based functions to the serverFunctions property | ||
functionNames.forEach(function (functionName) { | ||
_this.serverFunctions[functionName] = function () { | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.GASClient = void 0; | ||
var is_gas_environment_1 = require("./utils/is-gas-environment"); | ||
var gas_promises_1 = require("./classes/gas-promises"); | ||
var server_proxy_1 = require("./classes/server-proxy"); | ||
var GASClient = /** @class */ (function () { | ||
function GASClient(_config) { | ||
this._config = _config; | ||
if (is_gas_environment_1.isGASEnvironment()) { | ||
this._functionHost = new gas_promises_1.GASPromises(); | ||
} | ||
return new Promise(function (resolve, reject) { | ||
var _google$script$run$wi; | ||
(_google$script$run$wi = google.script.run.withSuccessHandler(resolve).withFailureHandler(reject))[functionName].apply(_google$script$run$wi, args); | ||
}); | ||
}; | ||
}); | ||
} catch (err) { | ||
if (typeof google === 'undefined') { | ||
// we'll store and access the resolve/reject functions here by id | ||
window.gasStore = {}; // this domain should be restricted to googleusercontent.com but the subdomain is variable | ||
// supports window.location.origin as default for backward compatibility | ||
var targetOrigin = config.parentTargetOrigin || window.location.origin; // set up the message 'receive' handler | ||
var receiveMessageHandler = function receiveMessageHandler(event) { | ||
var allowedDevelopmentDomains = config.allowedDevelopmentDomains; // check the allow list for the receiving origin | ||
var allowOrigin = checkAllowList(allowedDevelopmentDomains, event.origin); | ||
if (!allowOrigin) return; // we only care about the type: 'RESPONSE' messages here | ||
if (event.data.type !== 'RESPONSE') return; | ||
var _event$data = event.data, | ||
response = _event$data.response, | ||
status = _event$data.status, | ||
id = _event$data.id; // look up the saved resolve and reject funtions in our global store based | ||
// on the response id, and call the function depending on the response status | ||
var _window$gasStore$id = window.gasStore[id], | ||
resolve = _window$gasStore$id.resolve, | ||
reject = _window$gasStore$id.reject; | ||
if (status === 'ERROR') { | ||
// TODO: return here so resolve doesn't get called on error | ||
reject(response); | ||
else { | ||
this._functionHost = new server_proxy_1.ServerProxy(this._config); | ||
} | ||
resolve(response); | ||
}; | ||
window.addEventListener('message', receiveMessageHandler, false); | ||
var handler = { | ||
get: function get(target, functionName) { | ||
var id = (0, _uuid.v4)(); | ||
var promise = new Promise(function (resolve, reject) { | ||
// store the new Promise's resolve and reject | ||
window.gasStore[id] = { | ||
resolve: resolve, | ||
reject: reject | ||
}; | ||
}); | ||
return function () { | ||
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
args[_key2] = arguments[_key2]; | ||
} | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage | ||
window.parent.postMessage({ | ||
type: 'REQUEST', | ||
id: id, | ||
functionName: functionName, | ||
args: [].concat(args) | ||
}, targetOrigin); | ||
return promise; | ||
}; | ||
} | ||
}; | ||
this.serverFunctions = new Proxy({}, handler); | ||
} | ||
} | ||
}; | ||
exports["default"] = Server; | ||
Object.defineProperty(GASClient.prototype, "serverFunctions", { | ||
get: function () { | ||
return this._functionHost.serverFunctions; | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
return GASClient; | ||
}()); | ||
exports.GASClient = GASClient; |
{ | ||
"name": "gas-client", | ||
"version": "0.3.0", | ||
"version": "1.0.0-pr.1", | ||
"description": "A client-side utility class that can call server-side Google Apps Script functions", | ||
"main": "build/index.js", | ||
"files": [ | ||
"build", | ||
"src" | ||
"src", | ||
"build" | ||
], | ||
"repository": "https://github.com/enuchi/gas-client.git", | ||
"author": "Elisha Nuchi", | ||
"contributors": [ | ||
"Guilherme Tod <guilhermetod@gmail.com> (https://github.com/guilhermetod)" | ||
], | ||
"license": "MIT", | ||
"scripts": { | ||
"prepack": "yarn build", | ||
"pretest": "yarn build", | ||
"test": "jest --coverage", | ||
"test:watch": "jest --coverage --watch", | ||
"prebuild": "rm -rf build", | ||
"build": "babel src/index.js -d build" | ||
"build": "tsc" | ||
}, | ||
@@ -25,3 +29,3 @@ "bugs": { | ||
"keywords": [ | ||
"Goole", | ||
"Google", | ||
"Apps", | ||
@@ -37,7 +41,11 @@ "Script", | ||
"@types/jest": "^26.0.15", | ||
"@types/node": "^14.6.2", | ||
"@types/uuid": "^8.3.0", | ||
"@typescript-eslint/eslint-plugin": "3.10.1", | ||
"@typescript-eslint/parser": "3.10.1", | ||
"babel-eslint": "^10.1.0", | ||
"eslint": "^7.5.0", | ||
"eslint-config-airbnb-base": "^14.2.0", | ||
"eslint-config-airbnb-typescript": "^9.0.0", | ||
"eslint-config-prettier": "^6.11.0", | ||
"eslint-plugin-babel": "^5.3.1", | ||
"eslint-plugin-import": "^2.22.0", | ||
@@ -47,3 +55,4 @@ "eslint-plugin-jest": "^24.1.3", | ||
"jest": "^26.6.3", | ||
"prettier": "^2.0.5" | ||
"prettier": "^2.0.5", | ||
"typescript": "^4.0.2" | ||
}, | ||
@@ -50,0 +59,0 @@ "dependencies": { |
@@ -1,6 +0,6 @@ | ||
# gas-client | ||
# gas-client v1.0.0 | ||
A client-side utility class that uses promises to call server-side Google Apps Script functions. This is a user-friendly wrapper of [google.script.run](https://developers.google.com/apps-script/guides/html/reference/run). | ||
It can also optionally be used in local development and is designed to interact with the [Google Apps Script Dev Server](https://github.com/enuchi/Google-Apps-Script-Webpack-Dev-Server) used in the [React / Google Apps Script](https://github.com/enuchi/React-Google-Apps-Script) project. | ||
It can also optionally be used in local development and is designed to work with [React Google Apps Script](https://github.com/enuchi/React-Google-Apps-Script) project. | ||
@@ -12,2 +12,3 @@ --- | ||
Install | ||
```bash | ||
@@ -20,4 +21,4 @@ > npm install gas-client | ||
```javascript | ||
import Server from 'gas-client'; | ||
const { serverFunctions } = new Server(); | ||
import { GASClient } from 'gas-client'; | ||
const { serverFunctions } = new GASClient(); | ||
@@ -36,5 +37,5 @@ // We now have access to all our server functions, which return promises | ||
```javascript | ||
import Server from 'gas-client'; | ||
import { GASClient } from 'gas-client'; | ||
const { serverFunctions } = new Server({ | ||
const { serverFunctions } = new GASClient({ | ||
allowedDevelopmentDomains: 'https://localhost:3000', | ||
@@ -67,4 +68,4 @@ }); | ||
// With this package we can now do this: | ||
import Server from 'gas-client'; | ||
const { serverFunctions } = new Server(); | ||
import { GASClient } from 'gas-client'; | ||
const { serverFunctions } = new GASClient(); | ||
@@ -92,11 +93,69 @@ // We now have access to all our server functions, which return promises | ||
## Typescript | ||
This project now supports typescript! | ||
To use it, simply import your server functions and pass them as a type parameter when creating your server. | ||
### On your server-side code | ||
```typescript | ||
// src/server/index.ts | ||
interface SheetData { | ||
name: string; | ||
numOfRows: number; | ||
} | ||
const getSheetData = (): SheetData => { | ||
const sheet = SpreadsheetApp.getActiveSheet(); | ||
return { | ||
name: sheet.getName(), | ||
numOfRows: sheet.getMaxRows(), | ||
}; | ||
}; | ||
const appendRowsToSheet = (sheetName: string, rowsToAdd: number): void => { | ||
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); | ||
sheet.insertRowsAfter(sheet.getMaxRows(), rowsToAdd); | ||
}; | ||
export { getSheetData, appendRowsToSheet }; | ||
``` | ||
### On your client-side code | ||
```typescript | ||
// src/client/add-rows.ts | ||
import { GASClient } from 'gas-client'; | ||
import { showUserPrompt } from './show-user-prompt'; | ||
import * as server from '../server'; | ||
const { serverFunctions } = new GASClient<typeof server>(); | ||
const promptUser = async (): Promise<void> => { | ||
const { name, numOfRows } = await serverFunctions.getSheetData(); | ||
const response = await showUserPrompt(`Sheet ${name} has ${numOfRows} rows. How many would you like to add?`); | ||
serverFunctions.appendRowsToSheet(name, numOfRows); | ||
}; | ||
``` | ||
Now you can have your function names, parameters and return types checked. | ||
### Get better IDE support and catch errors ahead! | ||
![Get-better-IDE](https://i.imgur.com/gPmOPqX.gif) | ||
--- | ||
## API | ||
The config object takes: | ||
- `allowedDevelopmentDomains`: A config to specifiy which domains are permitted for communication with Google Apps Script Webpack Dev Server development tool. This is a security setting, and if not specified, will block functionality in development. `allowedDevelopmentDomains` will accept either a space-separated string of allowed subdomains, e.g. `'https://localhost:3000 https://localhost:8080'` (notice no trailing slashes); or a function that takes in the requesting origin and should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin);` | ||
- `parentTargetOrigin` An optional string to specify which parent window domain this client can send communication to. Defaults to own domain for backward compatibility with Google Apps Script Webpack Dev Server development tool (default uses domain where the client is running, e.g. localhost). Can be '*' to allow all parent domains if parent is unknown or variable. | ||
- `allowedDevelopmentDomains`: A config to specifiy which domains are permitted for communication with Google Apps Script Webpack Dev Server development tool. This is a security setting, and if not specified, will block functionality in development. `allowedDevelopmentDomains` will accept either a space-separated string of allowed subdomains, e.g. `'https://localhost:3000 https://localhost:8080'` (notice no trailing slashes); or a function that should expect one argument, the requesting origin, and should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin);` | ||
### Production mode | ||
In the normal Google Apps Script production environment, `new Server()` will have one available method: | ||
In the normal Google Apps Script production environment, a `new GASClient()` instance will have one available method: | ||
@@ -111,4 +170,15 @@ - `serverFunctions`: an object containing all publicly exposed server functions (see example above). | ||
Calling `new Server({ allowedDevelopmentDomains })` will create an instance with the following method in development mode: | ||
Calling `new GASClient({ allowedDevelopmentDomains })` will create an instance with the following method in development mode: | ||
- `serverFunctions`: a proxy object, used for development purposes, that mimics calling `google.script.run`. It will dispatch a message to the parent iframe (our custom Dev Server), which will call an app that actually interacts with the `google.script.run` API. Development mode will also handle the response and resolve or reject based on the response type. See the implementation for details on the event signature. | ||
## Contributors | ||
@guilhermetod - Addition of TypeScript support and general improvements to this project! | ||
## Change Log | ||
Breaking changes in v1.0.0: | ||
- `targetOrigin` is set to `'*'` due to deprecation of [Google Apps Script Dev Server](https://github.com/enuchi/Google-Apps-Script-Webpack-Dev-Server) and variability of the parent Google Apps Script environment's subdomains | ||
- The main class is exported as named `{ GASClient }` export instead of as default export |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35472
40
598
179
19
1