@pluv/platform-cloudflare 0.0.0-experimental-20250527040415
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/.turbo/turbo-build.log +16 -0
- package/.turbo/turbo-lint.log +5 -0
- package/CHANGELOG.md +1776 -0
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.mjs +412 -0
- package/eslint.config.mjs +4 -0
- package/package.json +38 -0
- package/src/CloudflarePlatform.ts +209 -0
- package/src/CloudflareWebSocket.ts +141 -0
- package/src/constants.ts +4 -0
- package/src/createPluvHandler.ts +244 -0
- package/src/index.ts +13 -0
- package/src/infer.ts +17 -0
- package/src/platformCloudflare.ts +57 -0
- package/src/utils/identity.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Shoubhit Dash
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## `@pluv/platform-cloudflare`
|
|
2
|
+
|
|
3
|
+
> Enables [@pluv/io](https://www.npmjs.com/package/@pluv/io) to run on [Cloudflare Workers](https://workers.cloudflare.com/).
|
|
4
|
+
|
|
5
|
+
**👉 See full documentation on [pluv.io](https://pluv.io/docs/introduction). 👈**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# npm
|
|
11
|
+
npm install @pluv/platform-cloudflare
|
|
12
|
+
|
|
13
|
+
# yarn
|
|
14
|
+
yarn add @pluv/platform-cloudflare
|
|
15
|
+
|
|
16
|
+
# pnpm
|
|
17
|
+
pnpm add @pluv/platform-cloudflare
|
|
18
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { AbstractWebSocket, WebSocketSession, WebSocketSerializedState, AbstractWebSocketConfig, AbstractEventMap, AbstractListener, AbstractPlatform, WebSocketRegistrationMode, AbstractPlatformConfig, ConvertWebSocketConfig, PluvServer, InferInitContextType, CreateIOParams, PluvIOAuthorize, PluvContext } from '@pluv/io';
|
|
2
|
+
import { IOAuthorize, JsonObject, InferIOAuthorizeUser, Json, MaybePromise, InferIOAuthorize, BaseUser, Maybe, Id } from '@pluv/types';
|
|
3
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
4
|
+
|
|
5
|
+
type CloudflareWebSocketConfig = AbstractWebSocketConfig;
|
|
6
|
+
declare class CloudflareWebSocket<TAuthorize extends IOAuthorize<any, any> | null = null> extends AbstractWebSocket<WebSocket, TAuthorize> {
|
|
7
|
+
set presence(presence: JsonObject | null);
|
|
8
|
+
get readyState(): 0 | 1 | 2 | 3;
|
|
9
|
+
get session(): WebSocketSession<TAuthorize>;
|
|
10
|
+
get sessionId(): string;
|
|
11
|
+
get state(): WebSocketSerializedState;
|
|
12
|
+
set state(state: WebSocketSerializedState);
|
|
13
|
+
set user(user: InferIOAuthorizeUser<TAuthorize>);
|
|
14
|
+
constructor(webSocket: WebSocket, config: CloudflareWebSocketConfig);
|
|
15
|
+
addEventListener<TType extends keyof AbstractEventMap>(type: TType, handler: AbstractListener<TType>): void;
|
|
16
|
+
close(code?: number | undefined, reason?: string | undefined): void;
|
|
17
|
+
send(message: string | ArrayBuffer | ArrayBufferView): void;
|
|
18
|
+
terminate(code?: number): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type CloudflarePlatformRoomContext<TEnv extends Record<string, any>, TMeta extends Record<string, Json>> = {
|
|
22
|
+
env: TEnv;
|
|
23
|
+
state: DurableObjectState;
|
|
24
|
+
} & (keyof TMeta extends never ? {
|
|
25
|
+
meta?: undefined;
|
|
26
|
+
} : {
|
|
27
|
+
meta: TMeta;
|
|
28
|
+
});
|
|
29
|
+
type CloudflarePlatformConfig<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> = AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>> & {
|
|
30
|
+
mode?: WebSocketRegistrationMode;
|
|
31
|
+
};
|
|
32
|
+
declare class CloudflarePlatform<TAuthorize extends IOAuthorize<any, any> | null = null, TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> extends AbstractPlatform<CloudflareWebSocket<TAuthorize>, {
|
|
33
|
+
env: TEnv;
|
|
34
|
+
request: Request;
|
|
35
|
+
}, CloudflarePlatformRoomContext<TEnv, TMeta>, {
|
|
36
|
+
authorize: {
|
|
37
|
+
secret: true;
|
|
38
|
+
};
|
|
39
|
+
handleMode: "io";
|
|
40
|
+
requireAuth: false;
|
|
41
|
+
registrationMode: WebSocketRegistrationMode;
|
|
42
|
+
listeners: {
|
|
43
|
+
onRoomDeleted: true;
|
|
44
|
+
onRoomMessage: true;
|
|
45
|
+
onStorageUpdated: true;
|
|
46
|
+
onUserConnected: true;
|
|
47
|
+
onUserDisconnected: true;
|
|
48
|
+
};
|
|
49
|
+
router: true;
|
|
50
|
+
}> {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly _config: {
|
|
53
|
+
authorize: {
|
|
54
|
+
secret: true;
|
|
55
|
+
};
|
|
56
|
+
handleMode: "io";
|
|
57
|
+
registrationMode: WebSocketRegistrationMode;
|
|
58
|
+
requireAuth: false;
|
|
59
|
+
listeners: {
|
|
60
|
+
onRoomDeleted: true;
|
|
61
|
+
onRoomMessage: true;
|
|
62
|
+
onStorageUpdated: true;
|
|
63
|
+
onUserConnected: true;
|
|
64
|
+
onUserDisconnected: true;
|
|
65
|
+
};
|
|
66
|
+
router: true;
|
|
67
|
+
};
|
|
68
|
+
readonly _name = "platformCloudflare";
|
|
69
|
+
constructor(config: CloudflarePlatformConfig<TEnv, TMeta>);
|
|
70
|
+
acceptWebSocket(webSocket: CloudflareWebSocket<TAuthorize>): Promise<void>;
|
|
71
|
+
convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): CloudflareWebSocket<TAuthorize>;
|
|
72
|
+
getLastPing(webSocket: CloudflareWebSocket<TAuthorize>): number | null;
|
|
73
|
+
getSerializedState(webSocket: WebSocket): WebSocketSerializedState | null;
|
|
74
|
+
getSessionId(webSocket: WebSocket): string | null;
|
|
75
|
+
getWebSockets(): readonly WebSocket[];
|
|
76
|
+
initialize(config: AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>>): this;
|
|
77
|
+
parseData(data: string | ArrayBuffer): Record<string, any>;
|
|
78
|
+
randomUUID(): string;
|
|
79
|
+
setSerializedState(webSocket: CloudflareWebSocket<TAuthorize>, state: WebSocketSerializedState): WebSocketSerializedState;
|
|
80
|
+
private _getDetachedState;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type AuthorizeFunctionContext<TPluvServer extends PluvServer<any, any, any, any>> = {
|
|
84
|
+
room: string;
|
|
85
|
+
} & InferInitContextType<TPluvServer extends PluvServer<infer IPlatform, any, any, any> ? IPlatform : never>;
|
|
86
|
+
type AuthorizeFunction<TPluvServer extends PluvServer<any, any, any, any>> = (ctx: AuthorizeFunctionContext<TPluvServer>) => MaybePromise<Maybe<InferIOAuthorizeUser<InferIOAuthorize<TPluvServer>>>>;
|
|
87
|
+
type CreatePluvHandlerConfig<TPluvServer extends PluvServer<any, any, any, any>, TEnv extends Record<string, any>> = {
|
|
88
|
+
binding: string;
|
|
89
|
+
endpoint?: string;
|
|
90
|
+
modify?: (request: Request, response: Response, env: TEnv) => MaybePromise<Response>;
|
|
91
|
+
io: TPluvServer;
|
|
92
|
+
} & (InferIOAuthorizeUser<InferIOAuthorize<TPluvServer>> extends BaseUser ? {
|
|
93
|
+
authorize: AuthorizeFunction<TPluvServer>;
|
|
94
|
+
} : {
|
|
95
|
+
authorize?: undefined;
|
|
96
|
+
});
|
|
97
|
+
type PluvHandlerFetch<TEnv extends Record<string, any> = {}> = (request: Request, env: TEnv) => Promise<Response | null>;
|
|
98
|
+
interface CreatePluvHandlerResult<TEnv extends Record<string, any> = {}> {
|
|
99
|
+
DurableObject: {
|
|
100
|
+
new (state: DurableObjectState, env: TEnv): DurableObject<TEnv>;
|
|
101
|
+
};
|
|
102
|
+
fetch: PluvHandlerFetch<TEnv>;
|
|
103
|
+
handler: ExportedHandler<TEnv>;
|
|
104
|
+
}
|
|
105
|
+
type InferCloudflarePluvHandlerEnv<TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>> = TPluvServer extends PluvServer<CloudflarePlatform<any, infer IEnv>, any, any, any> ? IEnv : {};
|
|
106
|
+
/**
|
|
107
|
+
* @deprecated Instructions will be provided on https://pluv.io on how to host this yourself instead.
|
|
108
|
+
* @date April 27, 2025
|
|
109
|
+
*/
|
|
110
|
+
declare const createPluvHandler: <TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>>(config: CreatePluvHandlerConfig<TPluvServer, Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>) => CreatePluvHandlerResult<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>;
|
|
111
|
+
|
|
112
|
+
declare const identity: <T extends unknown>(x: T) => T;
|
|
113
|
+
|
|
114
|
+
type InferCallback<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> = (i: typeof identity) => {
|
|
115
|
+
env?: (io: TEnv) => TEnv;
|
|
116
|
+
meta?: (io: TMeta) => TMeta;
|
|
117
|
+
};
|
|
118
|
+
declare const infer: <TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}>(callback: InferCallback<TEnv, TMeta>) => InferCallback<TEnv, TMeta>;
|
|
119
|
+
|
|
120
|
+
type PlatformCloudflareCreateIOParams<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}, TContext extends Record<string, any> = {}, TUser extends BaseUser | null = null> = Id<CloudflarePlatformConfig<TEnv, TMeta> & Omit<CreateIOParams<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext, TUser>, "authorize" | "context" | "platform"> & {
|
|
121
|
+
authorize?: PluvIOAuthorize<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TUser, InferInitContextType<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>>>;
|
|
122
|
+
context?: PluvContext<CloudflarePlatform<IOAuthorize<TUser, any>, TEnv, TMeta>, TContext>;
|
|
123
|
+
types?: InferCallback<TEnv, TMeta>;
|
|
124
|
+
}>;
|
|
125
|
+
declare const platformCloudflare: <TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}, TContext extends Record<string, any> = {}, TUser extends BaseUser | null = null>(config?: PlatformCloudflareCreateIOParams<TEnv, TMeta, TContext, TUser>) => CreateIOParams<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext, TUser>;
|
|
126
|
+
|
|
127
|
+
export { type AuthorizeFunction, type AuthorizeFunctionContext, CloudflarePlatform, type CloudflarePlatformConfig, type CreatePluvHandlerConfig, type CreatePluvHandlerResult, type InferCallback, type PlatformCloudflareCreateIOParams, type PluvHandlerFetch, createPluvHandler, infer, platformCloudflare };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __async = (__this, __arguments, generator) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
var fulfilled = (value) => {
|
|
23
|
+
try {
|
|
24
|
+
step(generator.next(value));
|
|
25
|
+
} catch (e) {
|
|
26
|
+
reject(e);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var rejected = (value) => {
|
|
30
|
+
try {
|
|
31
|
+
step(generator.throw(value));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
reject(e);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
37
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/createPluvHandler.ts
|
|
42
|
+
import { DurableObject as BaseDurableObject } from "cloudflare:workers";
|
|
43
|
+
import { match } from "path-to-regexp";
|
|
44
|
+
|
|
45
|
+
// src/constants.ts
|
|
46
|
+
var DEFAULT_REGISTRATION_MODE = "detached";
|
|
47
|
+
var GARBAGE_COLLECT_INTERVAL_MS = 6e4;
|
|
48
|
+
|
|
49
|
+
// src/createPluvHandler.ts
|
|
50
|
+
var createPluvHandler = (config) => {
|
|
51
|
+
const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
|
|
52
|
+
const DurableObject = class extends BaseDurableObject {
|
|
53
|
+
constructor(state, env) {
|
|
54
|
+
super(state, env);
|
|
55
|
+
this._room = io.createRoom(state.id.toString(), { env, state });
|
|
56
|
+
}
|
|
57
|
+
webSocketClose(ws, code, reason, wasClean) {
|
|
58
|
+
return __async(this, null, function* () {
|
|
59
|
+
if (io._defs.platform._config.registrationMode !== "detached") return;
|
|
60
|
+
const onCloseHandler = this._room.onClose(ws);
|
|
61
|
+
yield onCloseHandler({ code, reason });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
webSocketError(ws, error) {
|
|
65
|
+
return __async(this, null, function* () {
|
|
66
|
+
if (io._defs.platform._config.registrationMode !== "detached") return;
|
|
67
|
+
const onErrorHandler = this._room.onError(ws);
|
|
68
|
+
const eventError = error instanceof Error ? error : new Error("Internal Error");
|
|
69
|
+
yield onErrorHandler({ error: eventError, message: eventError.message });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
webSocketMessage(ws, message) {
|
|
73
|
+
return __async(this, null, function* () {
|
|
74
|
+
if (io._defs.platform._config.registrationMode !== "detached") return;
|
|
75
|
+
const onMessageHandler = this._room.onMessage(ws);
|
|
76
|
+
yield onMessageHandler({ data: message });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
fetch(request) {
|
|
80
|
+
return __async(this, null, function* () {
|
|
81
|
+
const isWSRequest = request.headers.get("Upgrade") === "websocket";
|
|
82
|
+
if (!isWSRequest) return new Response("Expected websocket", { status: 400 });
|
|
83
|
+
const { 0: client, 1: server } = new WebSocketPair();
|
|
84
|
+
const token = new URL(request.url).searchParams.get("token");
|
|
85
|
+
const alarm = yield this.ctx.storage.getAlarm();
|
|
86
|
+
if (alarm !== null)
|
|
87
|
+
yield this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
|
|
88
|
+
yield this._room.register(server, { env: this.env, request, token });
|
|
89
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
alarm(alarmInfo) {
|
|
93
|
+
return __async(this, null, function* () {
|
|
94
|
+
yield this._room.garbageCollect();
|
|
95
|
+
yield this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const getDurableObjectNamespace = (env) => {
|
|
100
|
+
const namespace = env[binding];
|
|
101
|
+
if (!namespace) {
|
|
102
|
+
throw new Error(`Could not find DurableObject binding: ${binding}`);
|
|
103
|
+
}
|
|
104
|
+
return namespace;
|
|
105
|
+
};
|
|
106
|
+
const authHandler = (request, env) => __async(null, null, function* () {
|
|
107
|
+
if (!authorize) return null;
|
|
108
|
+
const { pathname, searchParams } = new URL(request.url);
|
|
109
|
+
const matcher = match(`${endpoint}/authorize`);
|
|
110
|
+
const matched = matcher(pathname);
|
|
111
|
+
if (!matched) return null;
|
|
112
|
+
const room = searchParams.get("room");
|
|
113
|
+
if (!room) {
|
|
114
|
+
return new Response("Not found", {
|
|
115
|
+
headers: { "Content-Type": "text/plain" },
|
|
116
|
+
status: 404
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const user = yield authorize({
|
|
121
|
+
env,
|
|
122
|
+
request,
|
|
123
|
+
room
|
|
124
|
+
});
|
|
125
|
+
if (!user) throw new Error();
|
|
126
|
+
const namespace = getDurableObjectNamespace(env);
|
|
127
|
+
const durableObjectId = namespace.idFromName(room);
|
|
128
|
+
const token = yield io.createToken({
|
|
129
|
+
env,
|
|
130
|
+
room: durableObjectId.toString(),
|
|
131
|
+
user,
|
|
132
|
+
request
|
|
133
|
+
});
|
|
134
|
+
return new Response(token, {
|
|
135
|
+
headers: { "Content-Type": "text/plain" },
|
|
136
|
+
status: 200
|
|
137
|
+
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
return new Response(err instanceof Error ? err.message : "Unauthorized", {
|
|
140
|
+
headers: { "Content-Type": "text/plain" },
|
|
141
|
+
status: 403
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const roomHandler = (request, env) => __async(null, null, function* () {
|
|
146
|
+
const { pathname } = new URL(request.url);
|
|
147
|
+
const matcher = match(`${endpoint}/room/:roomId`);
|
|
148
|
+
const matched = matcher(pathname);
|
|
149
|
+
if (!matched) return null;
|
|
150
|
+
const { roomId } = matched.params;
|
|
151
|
+
if (!roomId) {
|
|
152
|
+
return new Response("Not found", {
|
|
153
|
+
headers: { "Content-Type": "text/plain" },
|
|
154
|
+
status: 404
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const namespace = getDurableObjectNamespace(env);
|
|
158
|
+
const durableObjectId = namespace.idFromName(roomId);
|
|
159
|
+
const room = namespace.get(durableObjectId);
|
|
160
|
+
return room.fetch(request);
|
|
161
|
+
});
|
|
162
|
+
const handlerFetch = (request, env) => __async(null, null, function* () {
|
|
163
|
+
return [authHandler, roomHandler].reduce((promise, current) => __async(null, null, function* () {
|
|
164
|
+
return yield promise.then((value) => __async(null, null, function* () {
|
|
165
|
+
return value != null ? value : yield current(request, env);
|
|
166
|
+
}));
|
|
167
|
+
}), Promise.resolve(null));
|
|
168
|
+
});
|
|
169
|
+
const handler = {
|
|
170
|
+
fetch: (request, env) => __async(null, null, function* () {
|
|
171
|
+
var _a, _b;
|
|
172
|
+
const response = (_a = yield handlerFetch(request, env)) != null ? _a : new Response("Not Found", {
|
|
173
|
+
headers: { "Content-Type": "text/plain" },
|
|
174
|
+
status: 404
|
|
175
|
+
});
|
|
176
|
+
return (_b = modify == null ? void 0 : modify(request, response, env)) != null ? _b : response;
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
return { fetch: handlerFetch, DurableObject, handler };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/infer.ts
|
|
183
|
+
var infer = (callback) => callback;
|
|
184
|
+
|
|
185
|
+
// src/CloudflarePlatform.ts
|
|
186
|
+
import { AbstractPlatform } from "@pluv/io";
|
|
187
|
+
import { PersistenceCloudflareTransactionalStorage } from "@pluv/persistence-cloudflare-transactional-storage";
|
|
188
|
+
|
|
189
|
+
// src/CloudflareWebSocket.ts
|
|
190
|
+
import { AbstractWebSocket } from "@pluv/io";
|
|
191
|
+
var CloudflareWebSocket = class extends AbstractWebSocket {
|
|
192
|
+
set presence(presence) {
|
|
193
|
+
const deserialized = this.webSocket.deserializeAttachment();
|
|
194
|
+
const state = deserialized.state;
|
|
195
|
+
this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, this.webSocket.deserializeAttachment()), {
|
|
196
|
+
state: __spreadProps(__spreadValues({}, state), { presence })
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
get readyState() {
|
|
200
|
+
return this.webSocket.readyState;
|
|
201
|
+
}
|
|
202
|
+
get session() {
|
|
203
|
+
var _a;
|
|
204
|
+
const deserialized = this.webSocket.deserializeAttachment();
|
|
205
|
+
const sessionId = deserialized.sessionId;
|
|
206
|
+
const state = this.state;
|
|
207
|
+
const user = (_a = deserialized.user) != null ? _a : null;
|
|
208
|
+
return __spreadProps(__spreadValues({}, state), {
|
|
209
|
+
id: sessionId,
|
|
210
|
+
user,
|
|
211
|
+
webSocket: this
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
get sessionId() {
|
|
215
|
+
var _a, _b;
|
|
216
|
+
const deserialized = (_a = this.webSocket.deserializeAttachment()) != null ? _a : {};
|
|
217
|
+
const sessionId = (_b = deserialized.sessionId) != null ? _b : `p_${crypto.randomUUID()}`;
|
|
218
|
+
if (typeof deserialized.sessionId !== "string") {
|
|
219
|
+
this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { sessionId }));
|
|
220
|
+
}
|
|
221
|
+
return sessionId;
|
|
222
|
+
}
|
|
223
|
+
get state() {
|
|
224
|
+
var _a;
|
|
225
|
+
const deserialized = this.webSocket.deserializeAttachment();
|
|
226
|
+
const state = (_a = deserialized == null ? void 0 : deserialized.state) != null ? _a : null;
|
|
227
|
+
if (!state) throw new Error("Could not get websocket state");
|
|
228
|
+
const currentPing = state.timers.ping;
|
|
229
|
+
const lastPing = this._platform.getLastPing(this);
|
|
230
|
+
if (!lastPing) return state;
|
|
231
|
+
if (currentPing >= lastPing) return state;
|
|
232
|
+
const newState = __spreadProps(__spreadValues({}, state), {
|
|
233
|
+
timers: __spreadProps(__spreadValues({}, state.timers), { ping: lastPing })
|
|
234
|
+
});
|
|
235
|
+
return this._platform.setSerializedState(this, newState);
|
|
236
|
+
}
|
|
237
|
+
set state(state) {
|
|
238
|
+
const deserialized = this.webSocket.deserializeAttachment();
|
|
239
|
+
this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { state }));
|
|
240
|
+
}
|
|
241
|
+
set user(user) {
|
|
242
|
+
const deserialized = this.webSocket.deserializeAttachment();
|
|
243
|
+
this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { user }));
|
|
244
|
+
}
|
|
245
|
+
constructor(webSocket, config) {
|
|
246
|
+
const { room } = config;
|
|
247
|
+
super(webSocket, config);
|
|
248
|
+
const state = {
|
|
249
|
+
presence: null,
|
|
250
|
+
quit: false,
|
|
251
|
+
room,
|
|
252
|
+
timers: {
|
|
253
|
+
ping: (/* @__PURE__ */ new Date()).getTime(),
|
|
254
|
+
presence: null
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
webSocket.serializeAttachment(__spreadValues({
|
|
258
|
+
state
|
|
259
|
+
}, webSocket.deserializeAttachment()));
|
|
260
|
+
}
|
|
261
|
+
addEventListener(type, handler) {
|
|
262
|
+
this.webSocket.addEventListener(type, handler);
|
|
263
|
+
}
|
|
264
|
+
close(code, reason) {
|
|
265
|
+
const canClose = [this.CONNECTING, this.OPEN].some(
|
|
266
|
+
(readyState) => readyState === this.readyState
|
|
267
|
+
);
|
|
268
|
+
if (!canClose) return;
|
|
269
|
+
this.webSocket.close(code, reason);
|
|
270
|
+
}
|
|
271
|
+
send(message) {
|
|
272
|
+
if (this.readyState !== this.OPEN) return;
|
|
273
|
+
this.webSocket.send(message);
|
|
274
|
+
}
|
|
275
|
+
terminate(code = 1011) {
|
|
276
|
+
return this.webSocket.close(code, "Terminated");
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/CloudflarePlatform.ts
|
|
281
|
+
var CloudflarePlatform = class _CloudflarePlatform extends AbstractPlatform {
|
|
282
|
+
constructor(config) {
|
|
283
|
+
var _a, _b;
|
|
284
|
+
super(__spreadValues(__spreadValues({}, config), config.roomContext && config.mode === "detached" ? {
|
|
285
|
+
persistence: (_a = config.persistence) != null ? _a : new PersistenceCloudflareTransactionalStorage({ mode: "sqlite" })
|
|
286
|
+
} : {}));
|
|
287
|
+
this.id = crypto.randomUUID();
|
|
288
|
+
this._name = "platformCloudflare";
|
|
289
|
+
this._config = {
|
|
290
|
+
authorize: {
|
|
291
|
+
secret: true
|
|
292
|
+
},
|
|
293
|
+
handleMode: "io",
|
|
294
|
+
registrationMode: (_b = config.mode) != null ? _b : DEFAULT_REGISTRATION_MODE,
|
|
295
|
+
requireAuth: false,
|
|
296
|
+
listeners: {
|
|
297
|
+
onRoomDeleted: true,
|
|
298
|
+
onRoomMessage: true,
|
|
299
|
+
onStorageUpdated: true,
|
|
300
|
+
onUserConnected: true,
|
|
301
|
+
onUserDisconnected: true
|
|
302
|
+
},
|
|
303
|
+
router: true
|
|
304
|
+
};
|
|
305
|
+
const detachedState = this._getDetachedState();
|
|
306
|
+
if (!detachedState) return;
|
|
307
|
+
detachedState.setWebSocketAutoResponse(
|
|
308
|
+
new WebSocketRequestResponsePair(
|
|
309
|
+
'{"type":"$ping","data":{}}',
|
|
310
|
+
JSON.stringify({ type: "$pong", data: {} })
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
acceptWebSocket(webSocket) {
|
|
315
|
+
return __async(this, null, function* () {
|
|
316
|
+
const detachedState = this._getDetachedState();
|
|
317
|
+
if (!detachedState) {
|
|
318
|
+
webSocket.webSocket.accept();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
detachedState.acceptWebSocket(webSocket.webSocket);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
convertWebSocket(webSocket, config) {
|
|
325
|
+
const { room } = config;
|
|
326
|
+
return new CloudflareWebSocket(webSocket, {
|
|
327
|
+
persistence: this.persistence,
|
|
328
|
+
platform: this,
|
|
329
|
+
room
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
getLastPing(webSocket) {
|
|
333
|
+
var _a;
|
|
334
|
+
const detachedState = this._getDetachedState();
|
|
335
|
+
if (!detachedState) return null;
|
|
336
|
+
const timestamp = detachedState.getWebSocketAutoResponseTimestamp(webSocket.webSocket);
|
|
337
|
+
return (_a = timestamp == null ? void 0 : timestamp.getTime()) != null ? _a : null;
|
|
338
|
+
}
|
|
339
|
+
getSerializedState(webSocket) {
|
|
340
|
+
var _a;
|
|
341
|
+
const deserialized = webSocket.deserializeAttachment();
|
|
342
|
+
return (_a = deserialized == null ? void 0 : deserialized.state) != null ? _a : null;
|
|
343
|
+
}
|
|
344
|
+
getSessionId(webSocket) {
|
|
345
|
+
var _a;
|
|
346
|
+
const deserialized = (_a = webSocket.deserializeAttachment()) != null ? _a : {};
|
|
347
|
+
const sessionId = deserialized.sessionId;
|
|
348
|
+
if (typeof sessionId !== "string") return null;
|
|
349
|
+
return sessionId;
|
|
350
|
+
}
|
|
351
|
+
getWebSockets() {
|
|
352
|
+
var _a;
|
|
353
|
+
const detachedState = this._getDetachedState();
|
|
354
|
+
if (!detachedState) return [];
|
|
355
|
+
const webSockets = (_a = detachedState.getWebSockets()) != null ? _a : [];
|
|
356
|
+
return webSockets;
|
|
357
|
+
}
|
|
358
|
+
initialize(config) {
|
|
359
|
+
var _a;
|
|
360
|
+
const ctx = (_a = config.roomContext) != null ? _a : __spreadValues({}, this._roomContext);
|
|
361
|
+
if (!ctx.env || !ctx.state) throw new Error("Could not derive platform roomContext");
|
|
362
|
+
const roomContext = {
|
|
363
|
+
env: ctx.env,
|
|
364
|
+
meta: ctx.meta,
|
|
365
|
+
state: ctx.state
|
|
366
|
+
};
|
|
367
|
+
return new _CloudflarePlatform({
|
|
368
|
+
roomContext,
|
|
369
|
+
mode: this._config.registrationMode,
|
|
370
|
+
persistence: this.persistence.initialize(roomContext),
|
|
371
|
+
pubSub: this.pubSub
|
|
372
|
+
})._initialize();
|
|
373
|
+
}
|
|
374
|
+
parseData(data) {
|
|
375
|
+
if (typeof data === "string") return JSON.parse(data);
|
|
376
|
+
const decoder = new TextDecoder("utf8");
|
|
377
|
+
return JSON.parse(decoder.decode(data));
|
|
378
|
+
}
|
|
379
|
+
randomUUID() {
|
|
380
|
+
return crypto.randomUUID();
|
|
381
|
+
}
|
|
382
|
+
setSerializedState(webSocket, state) {
|
|
383
|
+
var _a;
|
|
384
|
+
const deserialized = (_a = webSocket.webSocket.deserializeAttachment()) != null ? _a : {};
|
|
385
|
+
webSocket.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { state }));
|
|
386
|
+
return state;
|
|
387
|
+
}
|
|
388
|
+
_getDetachedState() {
|
|
389
|
+
var _a, _b;
|
|
390
|
+
if (this._config.registrationMode !== "detached") return null;
|
|
391
|
+
const detachedState = (_b = (_a = this._roomContext) == null ? void 0 : _a.state) != null ? _b : null;
|
|
392
|
+
return detachedState;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/platformCloudflare.ts
|
|
397
|
+
var platformCloudflare = (config = {}) => {
|
|
398
|
+
const { authorize, context, crdt, debug, limits } = config;
|
|
399
|
+
return {
|
|
400
|
+
authorize,
|
|
401
|
+
context,
|
|
402
|
+
crdt,
|
|
403
|
+
debug,
|
|
404
|
+
limits,
|
|
405
|
+
platform: () => new CloudflarePlatform(config)
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
export {
|
|
409
|
+
createPluvHandler,
|
|
410
|
+
infer,
|
|
411
|
+
platformCloudflare
|
|
412
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pluv/platform-cloudflare",
|
|
3
|
+
"version": "0.0.0-experimental-20250527040415",
|
|
4
|
+
"description": "@pluv/io adapter for cloudflare workers",
|
|
5
|
+
"author": "leedavidcs",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/pluv-io/pluv",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pluv-io/pluv.git",
|
|
11
|
+
"directory": "packages/platform-cloudflare"
|
|
12
|
+
},
|
|
13
|
+
"module": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"path-to-regexp": "^8.2.0",
|
|
20
|
+
"@pluv/io": "^0.0.0-experimental-20250527040415",
|
|
21
|
+
"@pluv/persistence-cloudflare-transactional-storage": "^0.0.0-experimental-20250527040415",
|
|
22
|
+
"@pluv/types": "^0.0.0-experimental-20250527040415"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@cloudflare/workers-types": "^4.20250521.0",
|
|
26
|
+
"eslint": "^9.27.0",
|
|
27
|
+
"tsup": "^8.5.0",
|
|
28
|
+
"typescript": "^5.8.3",
|
|
29
|
+
"@pluv/tsconfig": "^0.0.0-experimental-20250527040415",
|
|
30
|
+
"eslint-config-pluv": "^0.0.0-experimental-20250527040415"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup src/index.ts",
|
|
34
|
+
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
35
|
+
"lint": "eslint \"src/**/*.ts*\" --fix --max-warnings 0",
|
|
36
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
|
37
|
+
}
|
|
38
|
+
}
|