Skip to content

Commit 6cd958b

Browse files
committed
Initial Commit
0 parents  commit 6cd958b

18 files changed

+4104
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

README.md

Whitespace-only changes.

packages/google/.clasp.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"scriptId": "1TZ1PwnI4cHtDHHUWiVbczUovl27Ajbw4TYgpvfDCn6h_OCNjUDZAaNdx"
3+
}

packages/google/.claspignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# ignore all files…
2+
3+
**/**
4+
5+
src/**
6+
7+
# ignore even valid files if in…
8+
9+
.git/**
10+
node_modules/**
11+
12+
!dist/**
13+
!appsscript.json

packages/google/appsscript.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"timeZone": "America/New_York",
3+
"dependencies": {
4+
"libraries": [
5+
{
6+
"userSymbol": "OAuth2",
7+
"version": "43",
8+
"libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF"
9+
}
10+
]
11+
},
12+
"exceptionLogging": "STACKDRIVER",
13+
"oauthScopes": [
14+
"https://www.googleapis.com/auth/drive.addons.metadata.readonly",
15+
"https://www.googleapis.com/auth/gmail.addons.current.action.compose",
16+
"https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
17+
"https://www.googleapis.com/auth/gmail.addons.execute",
18+
"https://www.googleapis.com/auth/script.locale",
19+
"https://www.googleapis.com/auth/workspace.linkpreview",
20+
"https://www.googleapis.com/auth/script.external_request",
21+
"https://www.googleapis.com/auth/drive.file",
22+
"https://www.googleapis.com/auth/documents",
23+
"https://www.googleapis.com/auth/script.container.ui"
24+
],
25+
"runtimeVersion": "V8",
26+
"addOns": {
27+
"common": {
28+
"name": "Mermaid Chart",
29+
"logoUrl": "https://i.ibb.co/nPcBc9z/icon-1.png",
30+
"useLocaleFromApp": true,
31+
"universalActions": [
32+
{
33+
"label": "Learn more about Mermaid Chart",
34+
"openLink": "https://www.mermaidchart.com"
35+
}
36+
],
37+
"layoutProperties": {
38+
"primaryColor": "#424242",
39+
"secondaryColor": "#ff3670"
40+
}
41+
},
42+
"gmail": {
43+
"contextualTriggers": [
44+
{
45+
"unconditional": {},
46+
"onTriggerFunction": "onGmailMessage"
47+
}
48+
],
49+
"composeTrigger": {
50+
"selectActions": [
51+
{
52+
"text": "Insert Diagram",
53+
"runFunction": "onGmailCompose"
54+
}
55+
],
56+
"draftAccess": "NONE"
57+
}
58+
},
59+
"docs": {
60+
"homepageTrigger": {
61+
"runFunction": "onDocsHomepage"
62+
},
63+
"linkPreviewTriggers": [
64+
{
65+
"patterns": [
66+
{
67+
"hostPattern": "mermaidchart.com"
68+
},
69+
{
70+
"hostPattern": "whole-tires-fix.loca.lt"
71+
}
72+
],
73+
"runFunction": "diagramLinkPreview",
74+
"labelText": "Mermaid Diagram",
75+
"logoUrl": "https://i.ibb.co/nPcBc9z/icon-1.png"
76+
}
77+
]
78+
}
79+
}
80+
}

packages/google/babel.config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
presets: [
3+
[
4+
// ES features necessary for user's Node version
5+
require("@babel/preset-env").default,
6+
{
7+
targets: {
8+
node: "current",
9+
},
10+
},
11+
],
12+
[require("@babel/preset-typescript").default],
13+
],
14+
plugins: [
15+
// Polyfills the runtime needed for async/await, generators, and friends
16+
// https://babeljs.io/docs/en/babel-plugin-transform-runtime
17+
[require("@babel/plugin-transform-runtime").default],
18+
],
19+
};

packages/google/package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "mermaidchart-google",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "",
6+
"scripts": {
7+
"login": "clasp login",
8+
"push": "clasp push",
9+
"watch": "clasp push --watch",
10+
"pull": "clasp pull",
11+
"build": "rollup -c --bundleConfigAsCjs",
12+
"build:watch": "rollup -c --bundleConfigAsCjs --watch"
13+
},
14+
"keywords": [],
15+
"author": "",
16+
"license": "ISC",
17+
"devDependencies": {
18+
"@babel/core": "^7.22.9",
19+
"@babel/plugin-transform-runtime": "^7.22.9",
20+
"@babel/preset-env": "^7.22.9",
21+
"@babel/preset-typescript": "^7.22.5",
22+
"@google/clasp": "^2.4.2",
23+
"@rollup/plugin-babel": "^6.0.3",
24+
"@rollup/plugin-node-resolve": "^15.1.0",
25+
"@types/google-apps-script": "^1.0.65",
26+
"@types/google-apps-script-oauth2": "^38.0.0",
27+
"@types/node": "^20.4.2",
28+
"@types/uuid": "^9.0.2",
29+
"rollup": "^3.26.3",
30+
"typescript": "^5.1.6"
31+
},
32+
"dependencies": {
33+
"js-base64": "^3.7.5",
34+
"uuid": "^9.0.0"
35+
}
36+
}

packages/google/rollup.config.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { babel } from "@rollup/plugin-babel";
2+
import { nodeResolve } from "@rollup/plugin-node-resolve";
3+
4+
const extensions = [".ts", ".js"];
5+
6+
const preventTreeShakingPlugin = () => {
7+
return {
8+
name: "no-treeshaking",
9+
resolveId(id, importer) {
10+
if (!importer) {
11+
// let's not treeshake entry points, as we're not exporting anything in App Scripts
12+
return { id, moduleSideEffects: "no-treeshake" };
13+
}
14+
return null;
15+
},
16+
};
17+
};
18+
19+
export default {
20+
input: "./src/Code.ts",
21+
output: {
22+
dir: "./dist/",
23+
format: "cjs",
24+
},
25+
plugins: [
26+
preventTreeShakingPlugin(),
27+
nodeResolve({
28+
extensions,
29+
mainFields: ["jsnext:main", "main"],
30+
}),
31+
babel({ extensions, babelHelpers: "runtime" }),
32+
],
33+
};

packages/google/src/Api.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { MCDocument, CacheKeys, URLS, TimeInSeconds, MCProject } from './Common';
2+
3+
export const baseURL = 'https://whole-tires-fix.loca.lt';
4+
5+
/**
6+
* Attempts to access a non-Google API using a constructed service
7+
* object.
8+
*
9+
* If your add-on needs access to non-Google APIs that require OAuth,
10+
* you need to implement this method. You can use the OAuth1 and
11+
* OAuth2 Apps Script libraries to help implement it.
12+
*
13+
* @param url The URL to access.
14+
* @param method_opt The HTTP method. Defaults to GET.
15+
* @param headers_opt The HTTP headers. Defaults to an empty
16+
* object. The Authorization field is added
17+
* to the headers in this method.
18+
* @return {HttpResponse} the result from the UrlFetchApp.fetch() call.
19+
*/
20+
export function accessProtectedResource(
21+
url: string,
22+
method: GoogleAppsScript.URL_Fetch.HttpMethod = 'get',
23+
headers: GoogleAppsScript.URL_Fetch.HttpHeaders = {}
24+
) {
25+
if (!url.startsWith('http')) {
26+
url = baseURL + url;
27+
}
28+
const service = getOAuthService();
29+
let maybeAuthorized = service.hasAccess();
30+
if (maybeAuthorized) {
31+
// A token is present, but it may be expired or invalid. Make a
32+
// request and check the response code to be sure.
33+
34+
// Make the UrlFetch request and return the result.
35+
const accessToken = service.getAccessToken();
36+
headers['Authorization'] = Utilities.formatString('Bearer %s', accessToken);
37+
const resp = UrlFetchApp.fetch(url, {
38+
headers: headers,
39+
method: method,
40+
muteHttpExceptions: true // Prevents thrown HTTP exceptions.
41+
});
42+
43+
const code = resp.getResponseCode();
44+
if (code >= 200 && code < 300) {
45+
return resp;
46+
} else if (code == 401 || code == 403) {
47+
// Not fully authorized for this action.
48+
maybeAuthorized = false;
49+
} else {
50+
// Handle other response codes by logging them and throwing an exception.
51+
console.error('Backend server error (%s): %s', code.toString(), resp.getContentText('utf-8'));
52+
throw 'Backend server error: ' + code;
53+
}
54+
}
55+
56+
if (!maybeAuthorized) {
57+
// Invoke the authorization flow using the default authorization prompt card.
58+
CardService.newAuthorizationException()
59+
.setAuthorizationUrl(service.getAuthorizationUrl())
60+
.setResourceDisplayName('Display name to show to the user')
61+
.throwException();
62+
}
63+
}
64+
65+
/**
66+
* Create a new OAuth service to facilitate accessing an API.
67+
* This example assumes there is a single service that the add-on needs to
68+
* access. Its name is used when persisting the authorized token, so ensure
69+
* it is unique within the scope of the property store. You must set the
70+
* client secret and client ID, which are obtained when registering your
71+
* add-on with the API.
72+
*
73+
* See the Apps Script OAuth2 Library documentation for more
74+
* information:
75+
* https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
76+
*
77+
* @return A configured OAuth2 service object.
78+
*/
79+
function getOAuthService() {
80+
return (
81+
OAuth2.createService('Mermaid Chart')
82+
.setAuthorizationBaseUrl('https://whole-tires-fix.loca.lt/oauth/authorize')
83+
.setTokenUrl('https://whole-tires-fix.loca.lt/oauth/token')
84+
.setClientId('f88f1365-dea8-466e-8880-e22211e145bd')
85+
.setParam('code_challenge_method', 'S256')
86+
.setScope('email')
87+
.setCallbackFunction('authCallback')
88+
// @ts-expect-error
89+
.generateCodeVerifier()
90+
.setCache(CacheService.getUserCache())
91+
.setPropertyStore(PropertiesService.getUserProperties())
92+
);
93+
}
94+
95+
/**
96+
* Boilerplate code to determine if a request is authorized and returns
97+
* a corresponding HTML message. When the user completes the OAuth2 flow
98+
* on the service provider's website, this function is invoked from the
99+
* service. In order for authorization to succeed you must make sure that
100+
* the service knows how to call this function by setting the correct
101+
* redirect URL.
102+
*
103+
* The redirect URL to enter is:
104+
* https://script.google.com/macros/d/<Apps Script ID>/usercallback
105+
*
106+
* See the Apps Script OAuth2 Library documentation for more
107+
* information:
108+
* https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
109+
*
110+
* @param {Object} callbackRequest The request data received from the
111+
* callback function. Pass it to the service's
112+
* handleCallback() method to complete the
113+
* authorization process.
114+
* @return {HtmlOutput} a success or denied HTML message to display to
115+
* the user. Also sets a timer to close the window
116+
* automatically.
117+
*/
118+
export function authCallback(callbackRequest: any) {
119+
const authorized = getOAuthService().handleCallback(callbackRequest);
120+
if (authorized) {
121+
return HtmlService.createHtmlOutput(
122+
'Success! You can close this tab now. <script>setTimeout(function() { top.window.close() }, 1);</script>'
123+
);
124+
} else {
125+
return HtmlService.createHtmlOutput('Denied');
126+
}
127+
}
128+
129+
/**
130+
* Unauthorizes the non-Google service. This is useful for OAuth
131+
* development/testing. Run this method (Run > resetOAuth in the script
132+
* editor) to reset OAuth to re-prompt the user for OAuth.
133+
*/
134+
export function resetOAuth() {
135+
getOAuthService().reset();
136+
}
137+
138+
export function cachedFetch<T>(key: string, url: string, ttl: number, fallback: string = '[]'): T {
139+
const cache = CacheService.getUserCache();
140+
const shouldUseCache = hasAccess() && true; // Set to false when testing to bypass cache.
141+
let value = shouldUseCache ? cache.get(key) : undefined;
142+
if (!value) {
143+
value = accessProtectedResource(url)?.getContentText() ?? fallback;
144+
cache.put(key, value, ttl);
145+
}
146+
return JSON.parse(value) as T;
147+
}
148+
149+
export function getDocuments(projectID: string): MCDocument[] {
150+
return cachedFetch(
151+
CacheKeys.documents(projectID),
152+
URLS.rest.projects.get(projectID).documents,
153+
TimeInSeconds.minutes(5)
154+
);
155+
}
156+
157+
export function getProjects(): MCProject[] {
158+
return cachedFetch(CacheKeys.projects, URLS.rest.projects.list, TimeInSeconds.day);
159+
}
160+
161+
function hasAccess() {
162+
const service = getOAuthService();
163+
const maybeAuthorized = service.hasAccess();
164+
return maybeAuthorized;
165+
}

0 commit comments

Comments
 (0)