multiplayer

This commit is contained in:
Jeff Emmett 2024-08-29 21:15:13 +02:00
parent c576c4e241
commit 7f94094de9
16 changed files with 39 additions and 12852 deletions

View File

@ -1,30 +0,0 @@
const urls = new Set();
function checkURL(request, init) {
const url =
request instanceof URL
? request
: new URL(
(typeof request === "string"
? new Request(request, init)
: request
).url
);
if (url.port && url.port !== "443" && url.protocol === "https:") {
if (!urls.has(url.toString())) {
urls.add(url.toString());
console.warn(
`WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` +
` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n`
);
}
}
}
globalThis.fetch = new Proxy(globalThis.fetch, {
apply(target, thisArg, argArray) {
const [request, init] = argArray;
checkURL(request, init);
return Reflect.apply(target, thisArg, argArray);
},
});

View File

@ -1,11 +0,0 @@
import worker, * as OTHER_EXPORTS from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\worker\\worker.ts";
import * as __MIDDLEWARE_0__ from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\node_modules\\wrangler\\templates\\middleware\\middleware-ensure-req-body-drained.ts";
import * as __MIDDLEWARE_1__ from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\node_modules\\wrangler\\templates\\middleware\\middleware-miniflare3-json-error.ts";
export * from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\worker\\worker.ts";
export const __INTERNAL_WRANGLER_MIDDLEWARE__ = [
__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default
]
export default worker;

View File

@ -1,134 +0,0 @@
// This loads all middlewares exposed on the middleware object and then starts
// the invocation chain. The big idea is that we can add these to the middleware
// export dynamically through wrangler, or we can potentially let users directly
// add them as a sort of "plugin" system.
import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\.wrangler\\tmp\\bundle-fO5AAb\\middleware-insertion-facade.js";
import { __facade_invoke__, __facade_register__, Dispatcher } from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\node_modules\\wrangler\\templates\\middleware\\common.ts";
import type { WorkerEntrypointConstructor } from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\.wrangler\\tmp\\bundle-fO5AAb\\middleware-insertion-facade.js";
// Preserve all the exports from the worker
export * from "C:\\Users\\jeffe\\Documents\\GitHub\\canvas-website\\.wrangler\\tmp\\bundle-fO5AAb\\middleware-insertion-facade.js";
class __Facade_ScheduledController__ implements ScheduledController {
readonly #noRetry: ScheduledController["noRetry"];
constructor(
readonly scheduledTime: number,
readonly cron: string,
noRetry: ScheduledController["noRetry"]
) {
this.#noRetry = noRetry;
}
noRetry() {
if (!(this instanceof __Facade_ScheduledController__)) {
throw new TypeError("Illegal invocation");
}
// Need to call native method immediately in case uncaught error thrown
this.#noRetry();
}
}
function wrapExportedHandler(worker: ExportedHandler): ExportedHandler {
// If we don't have any middleware defined, just return the handler as is
if (
__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||
__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0
) {
return worker;
}
// Otherwise, register all middleware once
for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {
__facade_register__(middleware);
}
const fetchDispatcher: ExportedHandlerFetchHandler = function (
request,
env,
ctx
) {
if (worker.fetch === undefined) {
throw new Error("Handler does not export a fetch() function.");
}
return worker.fetch(request, env, ctx);
};
return {
...worker,
fetch(request, env, ctx) {
const dispatcher: Dispatcher = function (type, init) {
if (type === "scheduled" && worker.scheduled !== undefined) {
const controller = new __Facade_ScheduledController__(
Date.now(),
init.cron ?? "",
() => {}
);
return worker.scheduled(controller, env, ctx);
}
};
return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);
},
};
}
function wrapWorkerEntrypoint(
klass: WorkerEntrypointConstructor
): WorkerEntrypointConstructor {
// If we don't have any middleware defined, just return the handler as is
if (
__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||
__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0
) {
return klass;
}
// Otherwise, register all middleware once
for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {
__facade_register__(middleware);
}
// `extend`ing `klass` here so other RPC methods remain callable
return class extends klass {
#fetchDispatcher: ExportedHandlerFetchHandler<Record<string, unknown>> = (
request,
env,
ctx
) => {
this.env = env;
this.ctx = ctx;
if (super.fetch === undefined) {
throw new Error("Entrypoint class does not define a fetch() function.");
}
return super.fetch(request);
};
#dispatcher: Dispatcher = (type, init) => {
if (type === "scheduled" && super.scheduled !== undefined) {
const controller = new __Facade_ScheduledController__(
Date.now(),
init.cron ?? "",
() => {}
);
return super.scheduled(controller);
}
};
fetch(request: Request<unknown, IncomingRequestCfProperties>) {
return __facade_invoke__(
request,
this.env,
this.ctx,
this.#dispatcher,
this.#fetchDispatcher
);
}
};
}
let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;
if (typeof ENTRY === "object") {
WRAPPED_ENTRY = wrapExportedHandler(ENTRY);
} else if (typeof ENTRY === "function") {
WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);
}
export default WRAPPED_ENTRY;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
"path": "."
},
{
"name": "orionreed.com",
"path": "../orionreed.com"
"name": "jeffemmett.com",
"path": "../jeffemmett.com"
}
],
"settings": {}

View File

@ -17,40 +17,41 @@
"dependencies": {
"@dimforge/rapier2d": "^0.11.2",
"@tldraw/sync": "^2.4.6",
"@tldraw/sync-core": "latest",
"@tldraw/tlschema": "latest",
"@types/markdown-it": "^14.1.1",
"@vercel/analytics": "^1.2.2",
"cloudflare-workers-unfurl": "^0.0.7",
"gray-matter": "^4.0.3",
"itty-router": "^5.0.17",
"lodash.throttle": "^4.1.1",
"markdown-it": "^14.1.0",
"markdown-it-latex2img": "^0.0.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.3",
"tldraw": "^2.4.6",
"@cloudflare/types": "^6.29.0",
"@tldraw/sync-core": "latest",
"@tldraw/tlschema": "latest",
"cloudflare-workers-unfurl": "^0.0.7",
"itty-router": "^5.0.17",
"lodash.throttle": "^4.1.1"
"tldraw": "^2.4.6"
},
"devDependencies": {
"@biomejs/biome": "1.4.1",
"@cloudflare/types": "^6.29.0",
"@cloudflare/workers-types": "^4.20240821.1",
"@types/lodash.throttle": "^4",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitejs/plugin-react": "^4.0.3",
"@vitejs/plugin-react-swc": "^3.6.0",
"concurrently": "^8.2.2",
"eslint": "^8.38.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"typescript": "^5.0.2",
"vite": "^5.3.3",
"vite-plugin-static-copy": "^1.0.6",
"vite-plugin-top-level-await": "^1.3.1",
"vite-plugin-wasm": "^3.2.2",
"wrangler": "^3.72.3",
"@types/lodash.throttle": "^4",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"concurrently": "^8.2.2",
"eslint": "^8.38.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4"
"wrangler": "^3.72.3"
}
}

View File

@ -82,7 +82,7 @@ async function unfurlBookmarkUrl({ url }: { url: string }): Promise<TLBookmarkAs
try {
const response = await fetch(`${WORKER_URL}/unfurl?url=${encodeURIComponent(url)}`)
const data = await response.json()
const data = await response.json() as { description: string, image: string, favicon: string, title: string }
asset.props.description = data?.description ?? ''
asset.props.image = data?.image ?? ''

View File

@ -22,9 +22,8 @@ export function Canvas({ shapes }: { shapes: TLShape[]; }) {
<Tldraw
components={components}
shapeUtils={[HTMLShapeUtil]}
onMount={(editor: Editor) => {
onMount={(_: Editor) => {
window.dispatchEvent(new CustomEvent('editorDidMountEvent'));
editor.user.updateUserPreferences({ isDarkMode: false })
}}
>
<SimController shapes={shapes} />

View File

@ -9,7 +9,7 @@ export function Inbox() {
const response = await fetch('https://jeffemmett-canvas.web.val.run', {
method: 'GET',
});
const messages = await response.json();
const messages = await response.json() as { id: string, from: string, subject: string, text: string }[];
for (let i = 0; i < messages.length; i++) {
const message = messages[i];

View File

@ -2,15 +2,16 @@
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"lib": ["ES2020"],
"module": "ESNext",
"lib": ["ES2022"],
"module": "ES2022",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"types": ["@cloudflare/workers-types"],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"target": "ES2022"
},
"include": ["worker"]
"include": ["worker/*.ts"]
}

View File

@ -8,8 +8,7 @@ import { viteStaticCopy } from 'vite-plugin-static-copy';
export default defineConfig({
define: {
'process.env.TLDRAW_WORKER_URL':
process.env.TLDRAW_WORKER_URL ?? '`http://${location.hostname}:5172`',
'process.env.TLDRAW_WORKER_URL': JSON.stringify('https://jeffemmett-canvas.jeffemmett.workers.dev')
},
plugins: [
react(),

View File

@ -1,3 +1,5 @@
/// <reference types="@cloudflare/workers-types" />
import { RoomSnapshot, TLSocketRoom } from '@tldraw/sync-core'
import {
TLRecord,
@ -69,6 +71,7 @@ export class TldrawDurableObject {
// Create the websocket pair for the client
const { 0: clientWebSocket, 1: serverWebSocket } = new WebSocketPair()
// @ts-ignore
serverWebSocket.accept()
// load the room, or retrieve it if it's already loaded

View File

@ -1,3 +1,5 @@
/// <reference types="@cloudflare/workers-types" />
import { IRequest, error } from 'itty-router'
import { Environment } from './types'
@ -36,6 +38,7 @@ export async function handleAssetDownload(
// if we have a cached response for this request (automatically handling ranges etc.), return it
const cacheKey = new Request(request.url, { headers: request.headers })
// @ts-ignore
const cachedResponse = await caches.default.match(cacheKey)
if (cachedResponse) {
return cachedResponse
@ -92,6 +95,7 @@ export async function handleAssetDownload(
// we only cache complete (200) responses
if (status === 200) {
const [cacheBody, responseBody] = body!.tee()
// @ts-ignore
ctx.waitUntil(caches.default.put(cacheKey, new Response(cacheBody, { headers, status })))
return new Response(responseBody, { headers, status })
}

View File

@ -1,6 +1,8 @@
// the contents of the environment should mostly be determined by wrangler.toml. These entries match
// the bindings defined there.
/// <reference types="@cloudflare/workers-types" />
export interface Environment {
TLDRAW_BUCKET: R2Bucket
TLDRAW_DURABLE_OBJECT: DurableObjectNamespace
}
}

View File

@ -9,7 +9,7 @@ export { TldrawDurableObject } from './TldrawDurableObject'
// we use itty-router (https://itty.dev/) to handle routing. in this example we turn on CORS because
// we're hosting the worker separately to the client. you should restrict this to your own domain.
const { preflight, corsify } = cors({ origin: '*' })
const router = AutoRouter<IRequest, [env: Environment, ctx: ExecutionContext]>({
const router = AutoRouter<IRequest, [env: Environment]>({
before: [preflight],
finally: [corsify],
catch: (e) => {