@pluv/platform-cloudflare 0.40.1 → 0.41.0

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pluv/platform-cloudflare@0.40.1 build /home/runner/work/pluv/pluv/packages/platform-cloudflare
2
+ > @pluv/platform-cloudflare@0.41.0 build /home/runner/work/pluv/pluv/packages/platform-cloudflare
3
3
  > tsup src/index.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,8 +8,8 @@
8
8
  CLI Using tsup config: /home/runner/work/pluv/pluv/packages/platform-cloudflare/tsup.config.ts
9
9
  CLI Target: es6
10
10
  ESM Build start
11
- ESM dist/index.mjs 12.93 KB
12
- ESM ⚡️ Build success in 50ms
11
+ ESM dist/index.mjs 13.90 KB
12
+ ESM ⚡️ Build success in 24ms
13
13
  DTS Build start
14
- DTS ⚡️ Build success in 1260ms
15
- DTS dist/index.d.mts 6.70 KB
14
+ DTS ⚡️ Build success in 1268ms
15
+ DTS dist/index.d.mts 7.18 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @pluv/platform-cloudflare
2
2
 
3
+ ## 0.41.0
4
+
5
+ ### Patch Changes
6
+
7
+ - d07cf2b: Update `createPluvHandler` to automatically garbage collect the dead websockets in the `IORoom` every 60 seconds.
8
+ - Updated dependencies [73adf21]
9
+ - Updated dependencies [f02a430]
10
+ - Updated dependencies [0b908b3]
11
+ - Updated dependencies [6422f3b]
12
+ - Updated dependencies [555b88d]
13
+ - Updated dependencies [a663c65]
14
+ - Updated dependencies [555b88d]
15
+ - @pluv/io@0.41.0
16
+ - @pluv/persistence-cloudflare-transactional-storage@0.41.0
17
+ - @pluv/types@0.41.0
18
+
19
+ ## 0.40.2
20
+
21
+ ### Patch Changes
22
+
23
+ - @pluv/io@0.40.2
24
+ - @pluv/persistence-cloudflare-transactional-storage@0.40.2
25
+ - @pluv/types@0.40.2
26
+
3
27
  ## 0.40.1
4
28
 
5
29
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,14 +1,16 @@
1
- import { AbstractWebSocket, WebSocketSerializedState, AbstractWebSocketConfig, AbstractEventMap, AbstractListener, AbstractPlatform, WebSocketRegistrationMode, AbstractPlatformConfig, ConvertWebSocketConfig, PluvServer, InferInitContextType, CreateIOParams, PluvIOAuthorize, PluvContext } from '@pluv/io';
2
- import { JsonObject, Json, MaybePromise, InferIOAuthorizeUser, InferIOAuthorize, BaseUser, Maybe, Id } from '@pluv/types';
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
3
  import { DurableObject } from 'cloudflare:workers';
4
4
 
5
5
  type CloudflareWebSocketConfig = AbstractWebSocketConfig;
6
- declare class CloudflareWebSocket extends AbstractWebSocket<WebSocket> {
6
+ declare class CloudflareWebSocket<TAuthorize extends IOAuthorize<any, any> | null = null> extends AbstractWebSocket<WebSocket, TAuthorize> {
7
7
  set presence(presence: JsonObject | null);
8
8
  get readyState(): 0 | 1 | 2 | 3;
9
+ get session(): WebSocketSession<TAuthorize>;
9
10
  get sessionId(): string;
10
11
  get state(): WebSocketSerializedState;
11
12
  set state(state: WebSocketSerializedState);
13
+ set user(user: InferIOAuthorizeUser<TAuthorize>);
12
14
  constructor(webSocket: WebSocket, config: CloudflareWebSocketConfig);
13
15
  addEventListener<TType extends keyof AbstractEventMap>(type: TType, handler: AbstractListener<TType>): void;
14
16
  close(code?: number | undefined, reason?: string | undefined): void;
@@ -27,7 +29,7 @@ type CloudflarePlatformRoomContext<TEnv extends Record<string, any>, TMeta exten
27
29
  type CloudflarePlatformConfig<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> = AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>> & {
28
30
  mode?: WebSocketRegistrationMode;
29
31
  };
30
- declare class CloudflarePlatform<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> extends AbstractPlatform<CloudflareWebSocket, {
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>, {
31
33
  env: TEnv;
32
34
  request: Request;
33
35
  }, CloudflarePlatformRoomContext<TEnv, TMeta>, {
@@ -64,16 +66,16 @@ declare class CloudflarePlatform<TEnv extends Record<string, any> = {}, TMeta ex
64
66
  };
65
67
  readonly _name = "platformCloudflare";
66
68
  constructor(config: CloudflarePlatformConfig<TEnv, TMeta>);
67
- acceptWebSocket(webSocket: CloudflareWebSocket): Promise<void>;
68
- convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): CloudflareWebSocket;
69
- getLastPing(webSocket: CloudflareWebSocket): number | null;
69
+ acceptWebSocket(webSocket: CloudflareWebSocket<TAuthorize>): Promise<void>;
70
+ convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): CloudflareWebSocket<TAuthorize>;
71
+ getLastPing(webSocket: CloudflareWebSocket<TAuthorize>): number | null;
70
72
  getSerializedState(webSocket: WebSocket): WebSocketSerializedState | null;
71
73
  getSessionId(webSocket: WebSocket): string | null;
72
74
  getWebSockets(): readonly WebSocket[];
73
75
  initialize(config: AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>>): this;
74
76
  parseData(data: string | ArrayBuffer): Record<string, any>;
75
77
  randomUUID(): string;
76
- setSerializedState(webSocket: CloudflareWebSocket, state: WebSocketSerializedState): WebSocketSerializedState;
78
+ setSerializedState(webSocket: CloudflareWebSocket<TAuthorize>, state: WebSocketSerializedState): WebSocketSerializedState;
77
79
  private _getDetachedState;
78
80
  }
79
81
 
@@ -99,8 +101,8 @@ interface CreatePluvHandlerResult<TEnv extends Record<string, any> = {}> {
99
101
  fetch: PluvHandlerFetch<TEnv>;
100
102
  handler: ExportedHandler<TEnv>;
101
103
  }
102
- type InferCloudflarePluvHandlerEnv<TPluvServer extends PluvServer<CloudflarePlatform, any, any, any>> = TPluvServer extends PluvServer<CloudflarePlatform<infer IEnv>, any, any, any> ? IEnv : {};
103
- declare const createPluvHandler: <TPluvServer extends PluvServer<CloudflarePlatform, any, any, any>>(config: CreatePluvHandlerConfig<TPluvServer, Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>) => CreatePluvHandlerResult<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>;
104
+ type InferCloudflarePluvHandlerEnv<TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>> = TPluvServer extends PluvServer<CloudflarePlatform<any, infer IEnv>, any, any, any> ? IEnv : {};
105
+ declare const createPluvHandler: <TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>>(config: CreatePluvHandlerConfig<TPluvServer, Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>) => CreatePluvHandlerResult<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>;
104
106
 
105
107
  declare const identity: <T extends unknown>(x: T) => T;
106
108
 
@@ -110,11 +112,11 @@ type InferCallback<TEnv extends Record<string, any> = {}, TMeta extends Record<s
110
112
  };
111
113
  declare const infer: <TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}>(callback: InferCallback<TEnv, TMeta>) => InferCallback<TEnv, TMeta>;
112
114
 
113
- 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<TEnv, TMeta>, TContext, TUser>, "authorize" | "context" | "platform"> & {
114
- authorize?: PluvIOAuthorize<CloudflarePlatform<TEnv, TMeta>, TUser, InferInitContextType<CloudflarePlatform<TEnv, TMeta>>>;
115
- context?: PluvContext<CloudflarePlatform<TEnv, TMeta>, TContext>;
115
+ 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"> & {
116
+ authorize?: PluvIOAuthorize<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TUser, InferInitContextType<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>>>;
117
+ context?: PluvContext<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext>;
116
118
  types?: InferCallback<TEnv, TMeta>;
117
119
  }>;
118
- 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<TEnv, TMeta>, TContext, TUser>;
120
+ 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>;
119
121
 
120
122
  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 CHANGED
@@ -41,6 +41,12 @@ var __async = (__this, __arguments, generator) => {
41
41
  // src/createPluvHandler.ts
42
42
  import { DurableObject as BaseDurableObject } from "cloudflare:workers";
43
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
44
50
  var createPluvHandler = (config) => {
45
51
  const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
46
52
  const DurableObject = class extends BaseDurableObject {
@@ -76,10 +82,18 @@ var createPluvHandler = (config) => {
76
82
  if (!isWSRequest) return new Response("Expected websocket", { status: 400 });
77
83
  const { 0: client, 1: server } = new WebSocketPair();
78
84
  const token = new URL(request.url).searchParams.get("token");
85
+ const alarm = yield this.ctx.storage.getAlarm();
86
+ if (alarm !== null) yield this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
79
87
  yield this._room.register(server, { env: this.env, request, token });
80
88
  return new Response(null, { status: 101, webSocket: client });
81
89
  });
82
90
  }
91
+ alarm(alarmInfo) {
92
+ return __async(this, null, function* () {
93
+ yield this._room.garbageCollect();
94
+ yield this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
95
+ });
96
+ }
83
97
  };
84
98
  const getDurableObjectNamespace = (env) => {
85
99
  const namespace = env[binding];
@@ -180,10 +194,22 @@ var CloudflareWebSocket = class extends AbstractWebSocket {
180
194
  get readyState() {
181
195
  return this.webSocket.readyState;
182
196
  }
197
+ get session() {
198
+ var _a;
199
+ const deserialized = this.webSocket.deserializeAttachment();
200
+ const sessionId = deserialized.sessionId;
201
+ const state = this.state;
202
+ const user = (_a = deserialized.user) != null ? _a : null;
203
+ return __spreadProps(__spreadValues({}, state), {
204
+ id: sessionId,
205
+ user,
206
+ webSocket: this
207
+ });
208
+ }
183
209
  get sessionId() {
184
210
  var _a, _b;
185
211
  const deserialized = (_a = this.webSocket.deserializeAttachment()) != null ? _a : {};
186
- const sessionId = (_b = deserialized.sessionId) != null ? _b : crypto.randomUUID();
212
+ const sessionId = (_b = deserialized.sessionId) != null ? _b : `p_${crypto.randomUUID()}`;
187
213
  if (typeof deserialized.sessionId !== "string") {
188
214
  this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { sessionId }));
189
215
  }
@@ -207,6 +233,10 @@ var CloudflareWebSocket = class extends AbstractWebSocket {
207
233
  const deserialized = this.webSocket.deserializeAttachment();
208
234
  this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { state }));
209
235
  }
236
+ set user(user) {
237
+ const deserialized = this.webSocket.deserializeAttachment();
238
+ this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { user }));
239
+ }
210
240
  constructor(webSocket, config) {
211
241
  const { room } = config;
212
242
  super(webSocket, config);
@@ -214,10 +244,12 @@ var CloudflareWebSocket = class extends AbstractWebSocket {
214
244
  presence: null,
215
245
  quit: false,
216
246
  room,
217
- timers: { ping: (/* @__PURE__ */ new Date()).getTime() }
247
+ timers: {
248
+ ping: (/* @__PURE__ */ new Date()).getTime(),
249
+ presence: null
250
+ }
218
251
  };
219
252
  webSocket.serializeAttachment(__spreadValues({
220
- sessionId: this.sessionId,
221
253
  state
222
254
  }, webSocket.deserializeAttachment()));
223
255
  }
@@ -238,9 +270,6 @@ var CloudflareWebSocket = class extends AbstractWebSocket {
238
270
  }
239
271
  };
240
272
 
241
- // src/constants.ts
242
- var DEFAULT_REGISTRATION_MODE = "detached";
243
-
244
273
  // src/CloudflarePlatform.ts
245
274
  var CloudflarePlatform = class _CloudflarePlatform extends AbstractPlatform {
246
275
  constructor(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/platform-cloudflare",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
4
4
  "description": "@pluv/io adapter for cloudflare workers",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,22 +17,22 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "path-to-regexp": "^8.2.0",
20
- "@pluv/io": "^0.40.1",
21
- "@pluv/persistence-cloudflare-transactional-storage": "^0.40.1",
22
- "@pluv/types": "^0.40.1"
20
+ "@pluv/io": "^0.41.0",
21
+ "@pluv/persistence-cloudflare-transactional-storage": "^0.41.0",
22
+ "@pluv/types": "^0.41.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@cloudflare/workers-types": "^4.20250414.0",
26
- "eslint": "^9.24.0",
25
+ "@cloudflare/workers-types": "^4.20250418.0",
26
+ "eslint": "^9.25.0",
27
27
  "tsup": "^8.4.0",
28
28
  "typescript": "^5.8.3",
29
- "@pluv/tsconfig": "^0.40.1",
30
- "eslint-config-pluv": "^0.40.1"
29
+ "@pluv/tsconfig": "^0.41.0",
30
+ "eslint-config-pluv": "^0.41.0"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsup src/index.ts",
34
34
  "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
35
- "lint": "eslint src/**/*.ts* --fix --max-warnings 0",
35
+ "lint": "eslint \"src/**/*.ts*\" --fix --max-warnings 0",
36
36
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
37
37
  }
38
38
  }
@@ -6,7 +6,7 @@ import type {
6
6
  } from "@pluv/io";
7
7
  import { AbstractPlatform } from "@pluv/io";
8
8
  import { PersistenceCloudflareTransactionalStorage } from "@pluv/persistence-cloudflare-transactional-storage";
9
- import type { Json } from "@pluv/types";
9
+ import type { IOAuthorize, Json } from "@pluv/types";
10
10
  import { CloudflareWebSocket } from "./CloudflareWebSocket";
11
11
  import { DEFAULT_REGISTRATION_MODE } from "./constants";
12
12
 
@@ -21,10 +21,11 @@ export type CloudflarePlatformConfig<
21
21
  > = AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>> & { mode?: WebSocketRegistrationMode };
22
22
 
23
23
  export class CloudflarePlatform<
24
+ TAuthorize extends IOAuthorize<any, any> | null = null,
24
25
  TEnv extends Record<string, any> = {},
25
26
  TMeta extends Record<string, Json> = {},
26
27
  > extends AbstractPlatform<
27
- CloudflareWebSocket,
28
+ CloudflareWebSocket<TAuthorize>,
28
29
  { env: TEnv; request: Request },
29
30
  CloudflarePlatformRoomContext<TEnv, TMeta>,
30
31
  {
@@ -81,7 +82,7 @@ export class CloudflarePlatform<
81
82
  );
82
83
  }
83
84
 
84
- public async acceptWebSocket(webSocket: CloudflareWebSocket): Promise<void> {
85
+ public async acceptWebSocket(webSocket: CloudflareWebSocket<TAuthorize>): Promise<void> {
85
86
  const detachedState = this._getDetachedState();
86
87
 
87
88
  if (!detachedState) {
@@ -93,13 +94,13 @@ export class CloudflarePlatform<
93
94
  detachedState.acceptWebSocket(webSocket.webSocket);
94
95
  }
95
96
 
96
- public convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): CloudflareWebSocket {
97
+ public convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): CloudflareWebSocket<TAuthorize> {
97
98
  const { room } = config;
98
99
 
99
- return new CloudflareWebSocket(webSocket, { persistence: this.persistence, platform: this, room });
100
+ return new CloudflareWebSocket<TAuthorize>(webSocket, { persistence: this.persistence, platform: this, room });
100
101
  }
101
102
 
102
- public getLastPing(webSocket: CloudflareWebSocket): number | null {
103
+ public getLastPing(webSocket: CloudflareWebSocket<TAuthorize>): number | null {
103
104
  const detachedState = this._getDetachedState();
104
105
 
105
106
  if (!detachedState) return null;
@@ -139,7 +140,7 @@ export class CloudflarePlatform<
139
140
 
140
141
  if (!roomContext.env || !roomContext.state) throw new Error("Could not derive platform roomContext");
141
142
 
142
- return new CloudflarePlatform<TEnv, TMeta>({
143
+ return new CloudflarePlatform<TAuthorize, TEnv, TMeta>({
143
144
  roomContext: {
144
145
  env: roomContext.env,
145
146
  meta: roomContext.meta,
@@ -164,7 +165,7 @@ export class CloudflarePlatform<
164
165
  }
165
166
 
166
167
  public setSerializedState(
167
- webSocket: CloudflareWebSocket,
168
+ webSocket: CloudflareWebSocket<TAuthorize>,
168
169
  state: WebSocketSerializedState,
169
170
  ): WebSocketSerializedState {
170
171
  const deserialized = webSocket.webSocket.deserializeAttachment() ?? {};
@@ -1,6 +1,12 @@
1
- import type { AbstractEventMap, AbstractListener, AbstractWebSocketConfig, WebSocketSerializedState } from "@pluv/io";
1
+ import type {
2
+ AbstractEventMap,
3
+ AbstractListener,
4
+ AbstractWebSocketConfig,
5
+ WebSocketSerializedState,
6
+ WebSocketSession,
7
+ } from "@pluv/io";
2
8
  import { AbstractWebSocket } from "@pluv/io";
3
- import type { JsonObject } from "@pluv/types";
9
+ import type { InferIOAuthorizeUser, IOAuthorize, JsonObject } from "@pluv/types";
4
10
 
5
11
  export interface CloudflareWebSocketEventMap {
6
12
  close: CloseEvent;
@@ -11,7 +17,10 @@ export interface CloudflareWebSocketEventMap {
11
17
 
12
18
  export type CloudflareWebSocketConfig = AbstractWebSocketConfig;
13
19
 
14
- export class CloudflareWebSocket extends AbstractWebSocket<WebSocket> {
20
+ export class CloudflareWebSocket<TAuthorize extends IOAuthorize<any, any> | null = null> extends AbstractWebSocket<
21
+ WebSocket,
22
+ TAuthorize
23
+ > {
15
24
  public set presence(presence: JsonObject | null) {
16
25
  const deserialized = this.webSocket.deserializeAttachment();
17
26
  const state = deserialized.state;
@@ -26,9 +35,23 @@ export class CloudflareWebSocket extends AbstractWebSocket<WebSocket> {
26
35
  return this.webSocket.readyState as 0 | 1 | 2 | 3;
27
36
  }
28
37
 
38
+ public get session(): WebSocketSession<TAuthorize> {
39
+ const deserialized = this.webSocket.deserializeAttachment();
40
+ const sessionId = deserialized.sessionId as string;
41
+ const state = this.state;
42
+ const user = (deserialized.user ?? null) as InferIOAuthorizeUser<TAuthorize>;
43
+
44
+ return {
45
+ ...state,
46
+ id: sessionId,
47
+ user,
48
+ webSocket: this,
49
+ };
50
+ }
51
+
29
52
  public get sessionId(): string {
30
53
  const deserialized = this.webSocket.deserializeAttachment() ?? {};
31
- const sessionId = deserialized.sessionId ?? crypto.randomUUID();
54
+ const sessionId = deserialized.sessionId ?? `p_${crypto.randomUUID()}`;
32
55
 
33
56
  if (typeof deserialized.sessionId !== "string") {
34
57
  this.webSocket.serializeAttachment({ ...deserialized, sessionId });
@@ -63,6 +86,12 @@ export class CloudflareWebSocket extends AbstractWebSocket<WebSocket> {
63
86
  this.webSocket.serializeAttachment({ ...deserialized, state });
64
87
  }
65
88
 
89
+ public set user(user: InferIOAuthorizeUser<TAuthorize>) {
90
+ const deserialized = this.webSocket.deserializeAttachment();
91
+
92
+ this.webSocket.serializeAttachment({ ...deserialized, user });
93
+ }
94
+
66
95
  constructor(webSocket: WebSocket, config: CloudflareWebSocketConfig) {
67
96
  const { room } = config;
68
97
 
@@ -72,11 +101,13 @@ export class CloudflareWebSocket extends AbstractWebSocket<WebSocket> {
72
101
  presence: null,
73
102
  quit: false,
74
103
  room,
75
- timers: { ping: new Date().getTime() },
104
+ timers: {
105
+ ping: new Date().getTime(),
106
+ presence: null,
107
+ },
76
108
  };
77
109
 
78
110
  webSocket.serializeAttachment({
79
- sessionId: this.sessionId,
80
111
  state,
81
112
  ...webSocket.deserializeAttachment(),
82
113
  });
package/src/constants.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import type { WebSocketRegistrationMode } from "@pluv/io";
2
2
 
3
3
  export const DEFAULT_REGISTRATION_MODE: WebSocketRegistrationMode = "detached";
4
+ export const GARBAGE_COLLECT_INTERVAL_MS = 60_000;
@@ -3,6 +3,7 @@ import type { BaseUser, Id, InferIOAuthorize, InferIOAuthorizeUser, Maybe, Maybe
3
3
  import { DurableObject as BaseDurableObject } from "cloudflare:workers";
4
4
  import { match } from "path-to-regexp";
5
5
  import { CloudflarePlatform } from "./CloudflarePlatform";
6
+ import { GARBAGE_COLLECT_INTERVAL_MS } from "./constants";
6
7
 
7
8
  export type AuthorizeFunctionContext<TPluvServer extends PluvServer<any, any, any, any>> = {
8
9
  room: string;
@@ -36,16 +37,16 @@ export interface CreatePluvHandlerResult<TEnv extends Record<string, any> = {}>
36
37
  handler: ExportedHandler<TEnv>;
37
38
  }
38
39
 
39
- type InferCloudflarePluvHandlerEnv<TPluvServer extends PluvServer<CloudflarePlatform, any, any, any>> =
40
- TPluvServer extends PluvServer<CloudflarePlatform<infer IEnv>, any, any, any> ? IEnv : {};
40
+ type InferCloudflarePluvHandlerEnv<TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>> =
41
+ TPluvServer extends PluvServer<CloudflarePlatform<any, infer IEnv>, any, any, any> ? IEnv : {};
41
42
 
42
- export const createPluvHandler = <TPluvServer extends PluvServer<CloudflarePlatform, any, any, any>>(
43
+ export const createPluvHandler = <TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>>(
43
44
  config: CreatePluvHandlerConfig<TPluvServer, Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>,
44
45
  ): CreatePluvHandlerResult<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> => {
45
46
  const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
46
47
 
47
48
  const DurableObject = class extends BaseDurableObject<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> {
48
- private _room: IORoom<CloudflarePlatform>;
49
+ private _room: IORoom<CloudflarePlatform<any, any>>;
49
50
 
50
51
  constructor(state: DurableObjectState, env: Id<InferCloudflarePluvHandlerEnv<TPluvServer>>) {
51
52
  super(state, env);
@@ -86,10 +87,18 @@ export const createPluvHandler = <TPluvServer extends PluvServer<CloudflarePlatf
86
87
  const { 0: client, 1: server } = new WebSocketPair();
87
88
  const token = new URL(request.url).searchParams.get("token");
88
89
 
90
+ const alarm = await this.ctx.storage.getAlarm();
91
+ if (alarm !== null) await this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
92
+
89
93
  await this._room.register(server, { env: this.env, request, token });
90
94
 
91
95
  return new Response(null, { status: 101, webSocket: client });
92
96
  }
97
+
98
+ public async alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> {
99
+ await this._room.garbageCollect();
100
+ await this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
101
+ }
93
102
  };
94
103
 
95
104
  const getDurableObjectNamespace = (env: Id<InferCloudflarePluvHandlerEnv<TPluvServer>>): DurableObjectNamespace => {
@@ -1,5 +1,5 @@
1
1
  import type { CreateIOParams, InferInitContextType, PluvContext, PluvIOAuthorize } from "@pluv/io";
2
- import type { BaseUser, Id, Json } from "@pluv/types";
2
+ import type { BaseUser, Id, IOAuthorize, Json } from "@pluv/types";
3
3
  import type { CloudflarePlatformConfig } from "./CloudflarePlatform";
4
4
  import { CloudflarePlatform } from "./CloudflarePlatform";
5
5
  import type { InferCallback } from "./infer";
@@ -11,13 +11,16 @@ export type PlatformCloudflareCreateIOParams<
11
11
  TUser extends BaseUser | null = null,
12
12
  > = Id<
13
13
  CloudflarePlatformConfig<TEnv, TMeta> &
14
- Omit<CreateIOParams<CloudflarePlatform<TEnv, TMeta>, TContext, TUser>, "authorize" | "context" | "platform"> & {
14
+ Omit<
15
+ CreateIOParams<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext, TUser>,
16
+ "authorize" | "context" | "platform"
17
+ > & {
15
18
  authorize?: PluvIOAuthorize<
16
- CloudflarePlatform<TEnv, TMeta>,
19
+ CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>,
17
20
  TUser,
18
- InferInitContextType<CloudflarePlatform<TEnv, TMeta>>
21
+ InferInitContextType<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>>
19
22
  >;
20
- context?: PluvContext<CloudflarePlatform<TEnv, TMeta>, TContext>;
23
+ context?: PluvContext<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext>;
21
24
  types?: InferCallback<TEnv, TMeta>;
22
25
  }
23
26
  >;
@@ -29,7 +32,7 @@ export const platformCloudflare = <
29
32
  TUser extends BaseUser | null = null,
30
33
  >(
31
34
  config: PlatformCloudflareCreateIOParams<TEnv, TMeta, TContext, TUser> = {},
32
- ): CreateIOParams<CloudflarePlatform<TEnv, TMeta>, TContext, TUser> => {
35
+ ): CreateIOParams<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext, TUser> => {
33
36
  const { authorize, context, crdt, debug } = config;
34
37
 
35
38
  return {
@@ -37,6 +40,6 @@ export const platformCloudflare = <
37
40
  context,
38
41
  crdt,
39
42
  debug,
40
- platform: new CloudflarePlatform<TEnv, TMeta>(config),
43
+ platform: new CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>(config),
41
44
  };
42
45
  };