google meet: add hardware whiteboard integration (#3765)

pushing out my changes but this is a draft. we need to do look into this
more:
- [ ] we might need to reach out to Google support and request to be put
on their "start a whiteboard" whitelist/partner list? it's actually
pretty unclear how to get on that list. I don't see any permissions/API
scopes that is meant to enable that 🤔
- [ ] but maybe this is something that you @steveruizok as Google
Workspace admin see on your end? let's look together when you get back.

I initially tried doing a bundle (using esbuild (and i tried
parcel/rollup too)) but it didn't feel like the right path, and also it
didn't work when loading it in the Apps Scripts.
So then I went the route of just doing an iframe and I think that feels
much better. This means though that we do want our iframe protector to
let through this usecase. But also, we could maybe just redirect always
to a new room? I'm not sure yet.

The build script helps either build the prod or staging version
depending on what you want.

Once we do find that the staging version works, then we'll go through
the process of:
- [x] applied for https://developers.google.com/workspace/preview
(already did this to get access to new Google Meet APIs just in case)
- [x] added to google analytics (already done)
- [ ] turn off testing mode for oauth and submit for review
- [ ] continue publishing process to Create a store listing for our prod
app: https://developers.google.com/workspace/marketplace/how-to-publish

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [ ] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [x] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [ ] `bugfix` — Bug fix
- [x] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know


### Release Notes

- Google Meet: add hardware whiteboard integration
This commit is contained in:
Mime Čuvalo 2024-05-20 15:52:05 +01:00 committed by GitHub
parent 16ba1eb2c2
commit 9ffd7f15ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1015 additions and 67 deletions

1
apps/apps-script/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.clasp.json

View file

@ -0,0 +1,33 @@
{
"addOns": {
"common": {
"layoutProperties": {
"primaryColor": "#000",
"secondaryColor": "#fff"
},
"logoUrl": "TLDRAW_HOST/android-chrome-maskable-512x512.png",
"name": "tldraw"
},
"meet": {
"web": {
"sidePanelUri": "TLDRAW_HOST/ts-side",
"mainStageUri": "TLDRAW_HOST/ts",
"supportsScreenSharing": true,
"logoUrl": "TLDRAW_HOST/android-chrome-maskable-512x512.png",
"addOnOrigins": ["TLDRAW_HOST"]
}
}
},
"timeZone": "Europe/London",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/documents.currentonly",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/workspace.linkpreview"
],
"urlFetchWhitelist": ["TLDRAW_HOST/"]
}

View file

@ -0,0 +1,30 @@
import { mkdirSync, writeFileSync } from 'fs'
import path from 'path'
import { exec } from '../../scripts/lib/exec'
import { readFileIfExists } from '../../scripts/lib/file'
async function build() {
await exec('rm', ['-rf', 'dist'])
mkdirSync('dist')
const appsScriptPath = './appsscript.json'
await exec('cp', ['-r', appsScriptPath, 'dist'])
const isProduction = process.env.IS_PRODUCTION === '1'
const scriptId = isProduction
? '1FWcAvz7Rl4iPXQX3KmXm2mNG_RK2kryS7Bja8Y7RHvuAHnic51p_pqe7'
: '1cJfZM0M_rGU-nYgG-4KR1DnERb7itkCsl1QmlqPxFvHnrz5n6Gfy8iht'
writeFileSync('./.clasp.json', `{"scriptId":"${scriptId}","rootDir":"./dist"}`)
const host = isProduction ? 'https://www.tldraw.com' : 'https://staging.tldraw.com'
await replaceInFile(appsScriptPath, 'TLDRAW_HOST', host)
}
async function replaceInFile(filename: string, searchValue: string, replaceValue: string) {
let contents = (await readFileIfExists(path.join('dist', filename))) ?? ''
contents = contents.replaceAll(searchValue, replaceValue)
writeFileSync(path.join('dist', filename), contents)
}
build()

View file

@ -0,0 +1,46 @@
{
"name": "apps-scripts",
"description": "tldraw for Google Apps",
"version": "2.0.0-beta.2",
"private": true,
"author": {
"name": "tldraw Inc.",
"email": "hello@tldraw.com"
},
"homepage": "https://tldraw.dev",
"license": "SEE LICENSE IN LICENSE.md",
"repository": {
"type": "git",
"url": "https://github.com/tldraw/tldraw"
},
"bugs": {
"url": "https://github.com/tldraw/tldraw/issues"
},
"keywords": [
"tldraw",
"drawing",
"app",
"development",
"whiteboard",
"canvas",
"infinite"
],
"scripts": {
"lint": "yarn run -T tsx ../../scripts/lint.ts",
"glogin": "clasp login",
"glogout": "clasp logout",
"gcreate": "clasp create --type standalone --title \"tldraw.com\" --rootDir ./dist",
"gpull": "yarn build && clasp pull",
"gpush": "yarn build && clasp push",
"gpull:staging": "yarn build:staging && clasp pull",
"gpush:staging": "yarn build:staging && clasp push",
"build": "IS_PRODUCTION=1 yarn run -T tsx build-workspace-app.ts",
"build:staging": "yarn run -T tsx build-workspace-app.ts"
},
"dependencies": {
"@google/clasp": "^2.4.2"
},
"devDependencies": {
"@types/google-apps-script": "^1.0.83"
}
}

View file

@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"experimentalDecorators": true,
"downlevelIteration": true,
"plugins": [
{
"name": "next"
}
]
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"references": []
}

View file

@ -34,6 +34,14 @@ exports[`the_routes 1`] = `
"reactRouterPattern": "/s/:roomId", "reactRouterPattern": "/s/:roomId",
"vercelRouterPattern": "^/s/[^/]*/?$", "vercelRouterPattern": "^/s/[^/]*/?$",
}, },
{
"reactRouterPattern": "/ts",
"vercelRouterPattern": "^/ts/?$",
},
{
"reactRouterPattern": "/ts-side",
"vercelRouterPattern": "^/ts-side/?$",
},
{ {
"reactRouterPattern": "/v/:roomId", "reactRouterPattern": "/v/:roomId",
"vercelRouterPattern": "^/v/[^/]*/?$", "vercelRouterPattern": "^/v/[^/]*/?$",

View file

@ -10,6 +10,7 @@ export const ROOM_CONTEXT = {
HISTORY_SNAPSHOT: 'history-snapshot', HISTORY_SNAPSHOT: 'history-snapshot',
HISTORY: 'history', HISTORY: 'history',
LOCAL: 'local', LOCAL: 'local',
PUBLIC_TOUCHSCREEN_APP: 'public-touchscreen-app',
} as const } as const
type $ROOM_CONTEXT = (typeof ROOM_CONTEXT)[keyof typeof ROOM_CONTEXT] type $ROOM_CONTEXT = (typeof ROOM_CONTEXT)[keyof typeof ROOM_CONTEXT]
@ -24,6 +25,7 @@ const WHITELIST_CONTEXT: $ROOM_CONTEXT[] = [
ROOM_CONTEXT.PUBLIC_MULTIPLAYER, ROOM_CONTEXT.PUBLIC_MULTIPLAYER,
ROOM_CONTEXT.PUBLIC_READONLY, ROOM_CONTEXT.PUBLIC_READONLY,
ROOM_CONTEXT.PUBLIC_SNAPSHOT, ROOM_CONTEXT.PUBLIC_SNAPSHOT,
ROOM_CONTEXT.PUBLIC_TOUCHSCREEN_APP,
] ]
function getEmbeddedState(context: $ROOM_CONTEXT) { function getEmbeddedState(context: $ROOM_CONTEXT) {

View file

@ -0,0 +1,24 @@
import { useEffect } from 'react'
import '../../styles/globals.css'
import { IFrameProtector, ROOM_CONTEXT } from '../components/IFrameProtector'
import { LocalEditor } from '../components/LocalEditor'
export function Component() {
useEffect(() => {
async function loadSession() {
const session = await (window as any).meet.addon.createAddonSession({
cloudProjectNumber: `${process.env.GOOGLE_CLOUD_PROJECT_NUMBER}`,
})
const mainStageClient = await session.createMainStageClient()
await mainStageClient.unloadSidePanel()
}
loadSession()
}, [])
return (
<IFrameProtector slug="ts" context={ROOM_CONTEXT.PUBLIC_TOUCHSCREEN_APP}>
<LocalEditor />
</IFrameProtector>
)
}

View file

@ -0,0 +1,28 @@
import { useEffect } from 'react'
import '../../styles/globals.css'
export function Component() {
useEffect(() => {
async function createSession() {
const session = await (window as any).meet.addon.createAddonSession({
cloudProjectNumber: `${process.env.GOOGLE_CLOUD_PROJECT_NUMBER}`,
})
const sidePanelClient = await session.createSidePanelClient()
await sidePanelClient.setCollaborationStartingState({
sidePanelUrl: `${window.location.origin}/tc-side`,
mainStageUrl: `${window.location.origin}/tc`,
additionalData: undefined,
})
}
createSession()
})
return (
<>
{/* eslint-disable @next/next/no-sync-scripts */}
<script src="https://www.gstatic.com/meetjs/addons/0.1.0/meet.addons.js"></script>
Starting a new session
</>
)
}

View file

@ -37,6 +37,8 @@ export const router = createRoutesFromElements(
<Route path="/" lazy={() => import('./pages/root')} /> <Route path="/" lazy={() => import('./pages/root')} />
<Route path={`/${ROOM_PREFIX}`} lazy={() => import('./pages/new')} /> <Route path={`/${ROOM_PREFIX}`} lazy={() => import('./pages/new')} />
<Route path="/new" lazy={() => import('./pages/new')} /> <Route path="/new" lazy={() => import('./pages/new')} />
<Route path={`/ts`} lazy={() => import('./pages/public-touchscreen-app')} />
<Route path={`/ts-side`} lazy={() => import('./pages/public-touchscreen-side-panel')} />
<Route path={`/${ROOM_PREFIX}/:roomId`} lazy={() => import('./pages/public-multiplayer')} /> <Route path={`/${ROOM_PREFIX}/:roomId`} lazy={() => import('./pages/public-multiplayer')} />
<Route path={`/${ROOM_PREFIX}/:boardId/history`} lazy={() => import('./pages/history')} /> <Route path={`/${ROOM_PREFIX}/:boardId/history`} lazy={() => import('./pages/history')} />
<Route <Route

880
yarn.lock

File diff suppressed because it is too large Load diff