@pluv/platform-cloudflare 0.1.18 → 0.2.1

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.1.18 build /home/runner/work/pluv/pluv/packages/platform-cloudflare
2
+ > @pluv/platform-cloudflare@0.2.1 build /home/runner/work/pluv/pluv/packages/platform-cloudflare
3
3
  > tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,10 +8,10 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- ESM dist/index.mjs 1.50 KB
12
- ESM ⚡️ Build success in 118ms
13
- CJS dist/index.js 2.52 KB
14
- CJS ⚡️ Build success in 119ms
11
+ ESM dist/index.mjs 5.53 KB
12
+ ESM ⚡️ Build success in 98ms
13
+ CJS dist/index.js 6.66 KB
14
+ CJS ⚡️ Build success in 100ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4258ms
17
- DTS dist/index.d.ts 1.03 KB
16
+ DTS ⚡️ Build success in 5163ms
17
+ DTS dist/index.d.ts 2.57 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @pluv/platform-cloudflare
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [8917309]
8
+ - @pluv/io@0.4.2
9
+
10
+ ## 0.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 643d5b5: added createPluvHandler to automatically set-up a pluv server with reasonable defaults
15
+
16
+ ### Patch Changes
17
+
18
+ - b85a232: bumped dependencies
19
+ - fde89cf: added defaults to the client to align it with createPluvClient by default
20
+ - Updated dependencies [b85a232]
21
+ - @pluv/io@0.4.1
22
+ - @pluv/types@0.2.0
23
+
3
24
  ## 0.1.18
4
25
 
5
26
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { AbstractWebSocket, AbstractEventMap, AbstractListener, AbstractWebSocketConfig, AbstractPlatform } from '@pluv/io';
1
+ import { AbstractWebSocket, AbstractEventMap, AbstractListener, AbstractWebSocketConfig, AbstractPlatform, PluvIO } from '@pluv/io';
2
+ import { MaybePromise, Maybe, InferIOAuthorizeUser, InferIOAuthorize, InferIOAuthorizeRequired } from '@pluv/types';
2
3
 
3
4
  type CloudflareWebSocketConfig = AbstractWebSocketConfig;
4
5
  declare class CloudflareWebSocket extends AbstractWebSocket {
@@ -18,6 +19,31 @@ declare class CloudflarePlatform extends AbstractPlatform<WebSocket> {
18
19
  randomUUID(): string;
19
20
  }
20
21
 
22
+ interface AuthorizeFunctionContext {
23
+ request: Request<any, CfProperties<any>>;
24
+ roomId: string;
25
+ }
26
+ type AuthorizeFunction<TPluv extends PluvIO<CloudflarePlatform>> = (ctx: AuthorizeFunctionContext) => MaybePromise<Maybe<InferIOAuthorizeUser<InferIOAuthorize<TPluv>>>>;
27
+ type CreatePluvHandlerConfig<TPluv extends PluvIO<CloudflarePlatform>, TBinding extends string, TEnv extends Record<string, any>> = {
28
+ binding: TBinding;
29
+ endpoint?: string;
30
+ modify?: (request: Request, response: Response, env: TEnv) => MaybePromise<Response>;
31
+ io: TPluv;
32
+ } & (InferIOAuthorizeRequired<InferIOAuthorize<TPluv>> extends true ? {
33
+ authorize: AuthorizeFunction<TPluv>;
34
+ } : {
35
+ authorize?: undefined;
36
+ });
37
+ type PluvHandlerFetch<TEnv extends Record<string, any> = {}> = (request: Request, env: TEnv) => Promise<Response | null>;
38
+ interface CreatePluvHandlerResult<TEnv extends Record<string, any> = {}> {
39
+ DurableObject: {
40
+ new (state: DurableObjectState): DurableObject;
41
+ };
42
+ fetch: PluvHandlerFetch<TEnv>;
43
+ handler: ExportedHandler<TEnv>;
44
+ }
45
+ declare const createPluvHandler: <TPluv extends PluvIO<CloudflarePlatform, any, any, any, any, any, any>, TBinding extends string, TEnv extends Record<string, any>>(config: CreatePluvHandlerConfig<TPluv, TBinding, TEnv>) => CreatePluvHandlerResult<TEnv>;
46
+
21
47
  declare const platformCloudflare: () => CloudflarePlatform;
22
48
 
23
- export { platformCloudflare };
49
+ export { AuthorizeFunction, AuthorizeFunctionContext, CreatePluvHandlerConfig, CreatePluvHandlerResult, PluvHandlerFetch, createPluvHandler, platformCloudflare };
package/dist/index.js CHANGED
@@ -16,14 +16,138 @@ var __copyProps = (to, from, except, desc) => {
16
16
  return to;
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __async = (__this, __arguments, generator) => {
20
+ return new Promise((resolve, reject) => {
21
+ var fulfilled = (value) => {
22
+ try {
23
+ step(generator.next(value));
24
+ } catch (e) {
25
+ reject(e);
26
+ }
27
+ };
28
+ var rejected = (value) => {
29
+ try {
30
+ step(generator.throw(value));
31
+ } catch (e) {
32
+ reject(e);
33
+ }
34
+ };
35
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
36
+ step((generator = generator.apply(__this, __arguments)).next());
37
+ });
38
+ };
19
39
 
20
40
  // src/index.ts
21
41
  var src_exports = {};
22
42
  __export(src_exports, {
43
+ createPluvHandler: () => createPluvHandler,
23
44
  platformCloudflare: () => platformCloudflare
24
45
  });
25
46
  module.exports = __toCommonJS(src_exports);
26
47
 
48
+ // src/createPluvHandler.ts
49
+ var import_path_to_regexp = require("path-to-regexp");
50
+ var createPluvHandler = (config) => {
51
+ const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
52
+ const DurableObject = class {
53
+ constructor(state) {
54
+ this._io = io.getRoom(state.id.toString());
55
+ }
56
+ fetch(request) {
57
+ return __async(this, null, function* () {
58
+ const isWSRequest = request.headers.get("Upgrade") === "websocket";
59
+ if (!isWSRequest) {
60
+ return new Response("Expected websocket", { status: 400 });
61
+ }
62
+ const { 0: client, 1: server } = new WebSocketPair();
63
+ const token = new URL(request.url).searchParams.get("token");
64
+ yield this._io.register(server, { token });
65
+ return new Response(null, { status: 101, webSocket: client });
66
+ });
67
+ }
68
+ };
69
+ const getDurableObjectNamespace = (env) => {
70
+ const namespace = env[binding];
71
+ if (!namespace) {
72
+ throw new Error(`Could not find DurableObject binding: ${binding}`);
73
+ }
74
+ return namespace;
75
+ };
76
+ const authHandler = (request, env) => __async(void 0, null, function* () {
77
+ if (!authorize)
78
+ return null;
79
+ const { pathname, searchParams } = new URL(request.url);
80
+ const matcher = (0, import_path_to_regexp.match)(`${endpoint}/authorize`);
81
+ const matched = matcher(pathname);
82
+ if (!matched)
83
+ return null;
84
+ const roomId = searchParams.get("room");
85
+ if (!roomId) {
86
+ return new Response("Not found", {
87
+ headers: { "Content-Type": "text/plain" },
88
+ status: 404
89
+ });
90
+ }
91
+ try {
92
+ const user = yield authorize({ request, roomId });
93
+ if (!user)
94
+ throw new Error();
95
+ const namespace = getDurableObjectNamespace(env);
96
+ const durableObjectId = namespace.idFromName(roomId);
97
+ const token = yield io.createToken({
98
+ room: durableObjectId.toString(),
99
+ user
100
+ });
101
+ return new Response(token, {
102
+ headers: { "Content-Type": "text/plain" },
103
+ status: 200
104
+ });
105
+ } catch (err) {
106
+ return new Response(
107
+ err instanceof Error ? err.message : "Unauthorized",
108
+ {
109
+ headers: { "Content-Type": "text/plain" },
110
+ status: 403
111
+ }
112
+ );
113
+ }
114
+ });
115
+ const roomHandler = (request, env) => __async(void 0, null, function* () {
116
+ const { pathname } = new URL(request.url);
117
+ const matcher = (0, import_path_to_regexp.match)(`${endpoint}/room/:roomId`);
118
+ const matched = matcher(pathname);
119
+ if (!matched)
120
+ return null;
121
+ const { roomId } = matched.params;
122
+ if (!roomId) {
123
+ return new Response("Not found", {
124
+ headers: { "Content-Type": "text/plain" },
125
+ status: 404
126
+ });
127
+ }
128
+ const namespace = getDurableObjectNamespace(env);
129
+ const durableObjectId = namespace.idFromName(roomId);
130
+ const room = namespace.get(durableObjectId);
131
+ return room.fetch(request);
132
+ });
133
+ const fetch = (request, env) => __async(void 0, null, function* () {
134
+ return [authHandler, roomHandler].reduce((promise, current) => {
135
+ return promise.then((value) => value != null ? value : current(request, env));
136
+ }, Promise.resolve(null));
137
+ });
138
+ const handler = {
139
+ fetch: (request, env) => __async(void 0, null, function* () {
140
+ var _a, _b;
141
+ const response = (_a = yield fetch(request, env)) != null ? _a : new Response("Not Found", {
142
+ headers: { "Content-Type": "text/plain" },
143
+ status: 404
144
+ });
145
+ return (_b = modify == null ? void 0 : modify(request, response, env)) != null ? _b : response;
146
+ })
147
+ };
148
+ return { fetch, DurableObject, handler };
149
+ };
150
+
27
151
  // src/CloudflarePlatform.ts
28
152
  var import_io2 = require("@pluv/io");
29
153
 
@@ -85,5 +209,6 @@ var platformCloudflare = () => {
85
209
  };
86
210
  // Annotate the CommonJS export names for ESM import in node:
87
211
  0 && (module.exports = {
212
+ createPluvHandler,
88
213
  platformCloudflare
89
214
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,127 @@
1
+ var __async = (__this, __arguments, generator) => {
2
+ return new Promise((resolve, reject) => {
3
+ var fulfilled = (value) => {
4
+ try {
5
+ step(generator.next(value));
6
+ } catch (e) {
7
+ reject(e);
8
+ }
9
+ };
10
+ var rejected = (value) => {
11
+ try {
12
+ step(generator.throw(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
18
+ step((generator = generator.apply(__this, __arguments)).next());
19
+ });
20
+ };
21
+
22
+ // src/createPluvHandler.ts
23
+ import { match } from "path-to-regexp";
24
+ var createPluvHandler = (config) => {
25
+ const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
26
+ const DurableObject = class {
27
+ constructor(state) {
28
+ this._io = io.getRoom(state.id.toString());
29
+ }
30
+ fetch(request) {
31
+ return __async(this, null, function* () {
32
+ const isWSRequest = request.headers.get("Upgrade") === "websocket";
33
+ if (!isWSRequest) {
34
+ return new Response("Expected websocket", { status: 400 });
35
+ }
36
+ const { 0: client, 1: server } = new WebSocketPair();
37
+ const token = new URL(request.url).searchParams.get("token");
38
+ yield this._io.register(server, { token });
39
+ return new Response(null, { status: 101, webSocket: client });
40
+ });
41
+ }
42
+ };
43
+ const getDurableObjectNamespace = (env) => {
44
+ const namespace = env[binding];
45
+ if (!namespace) {
46
+ throw new Error(`Could not find DurableObject binding: ${binding}`);
47
+ }
48
+ return namespace;
49
+ };
50
+ const authHandler = (request, env) => __async(void 0, null, function* () {
51
+ if (!authorize)
52
+ return null;
53
+ const { pathname, searchParams } = new URL(request.url);
54
+ const matcher = match(`${endpoint}/authorize`);
55
+ const matched = matcher(pathname);
56
+ if (!matched)
57
+ return null;
58
+ const roomId = searchParams.get("room");
59
+ if (!roomId) {
60
+ return new Response("Not found", {
61
+ headers: { "Content-Type": "text/plain" },
62
+ status: 404
63
+ });
64
+ }
65
+ try {
66
+ const user = yield authorize({ request, roomId });
67
+ if (!user)
68
+ throw new Error();
69
+ const namespace = getDurableObjectNamespace(env);
70
+ const durableObjectId = namespace.idFromName(roomId);
71
+ const token = yield io.createToken({
72
+ room: durableObjectId.toString(),
73
+ user
74
+ });
75
+ return new Response(token, {
76
+ headers: { "Content-Type": "text/plain" },
77
+ status: 200
78
+ });
79
+ } catch (err) {
80
+ return new Response(
81
+ err instanceof Error ? err.message : "Unauthorized",
82
+ {
83
+ headers: { "Content-Type": "text/plain" },
84
+ status: 403
85
+ }
86
+ );
87
+ }
88
+ });
89
+ const roomHandler = (request, env) => __async(void 0, null, function* () {
90
+ const { pathname } = new URL(request.url);
91
+ const matcher = match(`${endpoint}/room/:roomId`);
92
+ const matched = matcher(pathname);
93
+ if (!matched)
94
+ return null;
95
+ const { roomId } = matched.params;
96
+ if (!roomId) {
97
+ return new Response("Not found", {
98
+ headers: { "Content-Type": "text/plain" },
99
+ status: 404
100
+ });
101
+ }
102
+ const namespace = getDurableObjectNamespace(env);
103
+ const durableObjectId = namespace.idFromName(roomId);
104
+ const room = namespace.get(durableObjectId);
105
+ return room.fetch(request);
106
+ });
107
+ const fetch = (request, env) => __async(void 0, null, function* () {
108
+ return [authHandler, roomHandler].reduce((promise, current) => {
109
+ return promise.then((value) => value != null ? value : current(request, env));
110
+ }, Promise.resolve(null));
111
+ });
112
+ const handler = {
113
+ fetch: (request, env) => __async(void 0, null, function* () {
114
+ var _a, _b;
115
+ const response = (_a = yield fetch(request, env)) != null ? _a : new Response("Not Found", {
116
+ headers: { "Content-Type": "text/plain" },
117
+ status: 404
118
+ });
119
+ return (_b = modify == null ? void 0 : modify(request, response, env)) != null ? _b : response;
120
+ })
121
+ };
122
+ return { fetch, DurableObject, handler };
123
+ };
124
+
1
125
  // src/CloudflarePlatform.ts
2
126
  import { AbstractPlatform } from "@pluv/io";
3
127
 
@@ -60,5 +184,6 @@ var platformCloudflare = () => {
60
184
  return new CloudflarePlatform();
61
185
  };
62
186
  export {
187
+ createPluvHandler,
63
188
  platformCloudflare
64
189
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/platform-cloudflare",
3
- "version": "0.1.18",
3
+ "version": "0.2.1",
4
4
  "description": "@pluv/io adapter for cloudflare workers",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,15 +17,16 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@pluv/io": "^0.4.0",
20
+ "path-to-regexp": "^6.2.1",
21
+ "@pluv/io": "^0.4.2",
21
22
  "@pluv/types": "^0.2.0"
22
23
  },
23
24
  "devDependencies": {
24
- "@cloudflare/workers-types": "^4.20230404.0",
25
+ "@cloudflare/workers-types": "^4.20230511.0",
25
26
  "tsup": "^6.7.0",
26
27
  "typescript": "^5.0.4",
27
28
  "@pluv/tsconfig": "^0.1.6",
28
- "eslint-config-pluv": "^0.1.11"
29
+ "eslint-config-pluv": "^0.1.12"
29
30
  },
30
31
  "scripts": {
31
32
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -0,0 +1,184 @@
1
+ import { IORoom, PluvIO } from "@pluv/io";
2
+ import {
3
+ InferIOAuthorize,
4
+ InferIOAuthorizeRequired,
5
+ InferIOAuthorizeUser,
6
+ Maybe,
7
+ MaybePromise,
8
+ } from "@pluv/types";
9
+ import { match } from "path-to-regexp";
10
+ import { CloudflarePlatform } from "./CloudflarePlatform";
11
+
12
+ export interface AuthorizeFunctionContext {
13
+ request: Request<any, CfProperties<any>>;
14
+ roomId: string;
15
+ }
16
+ export type AuthorizeFunction<TPluv extends PluvIO<CloudflarePlatform>> = (
17
+ ctx: AuthorizeFunctionContext
18
+ ) => MaybePromise<Maybe<InferIOAuthorizeUser<InferIOAuthorize<TPluv>>>>;
19
+
20
+ export type CreatePluvHandlerConfig<
21
+ TPluv extends PluvIO<CloudflarePlatform>,
22
+ TBinding extends string,
23
+ TEnv extends Record<string, any>
24
+ > = {
25
+ binding: TBinding;
26
+ endpoint?: string;
27
+ modify?: (
28
+ request: Request,
29
+ response: Response,
30
+ env: TEnv
31
+ ) => MaybePromise<Response>;
32
+ io: TPluv;
33
+ } & (InferIOAuthorizeRequired<InferIOAuthorize<TPluv>> extends true
34
+ ? { authorize: AuthorizeFunction<TPluv> }
35
+ : { authorize?: undefined });
36
+
37
+ export type PluvHandlerFetch<TEnv extends Record<string, any> = {}> = (
38
+ request: Request,
39
+ env: TEnv
40
+ ) => Promise<Response | null>;
41
+
42
+ export interface CreatePluvHandlerResult<
43
+ TEnv extends Record<string, any> = {}
44
+ > {
45
+ DurableObject: { new (state: DurableObjectState): DurableObject };
46
+ fetch: PluvHandlerFetch<TEnv>;
47
+ handler: ExportedHandler<TEnv>;
48
+ }
49
+
50
+ export const createPluvHandler = <
51
+ TPluv extends PluvIO<CloudflarePlatform, any, any, any, any, any, any>,
52
+ TBinding extends string,
53
+ TEnv extends Record<string, any>
54
+ >(
55
+ config: CreatePluvHandlerConfig<TPluv, TBinding, TEnv>
56
+ ): CreatePluvHandlerResult<TEnv> => {
57
+ const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
58
+
59
+ const DurableObject = class implements DurableObject {
60
+ private _io: IORoom<CloudflarePlatform>;
61
+
62
+ constructor(state: DurableObjectState) {
63
+ this._io = io.getRoom(state.id.toString());
64
+ }
65
+
66
+ async fetch(
67
+ request: Request<any, CfProperties<any>>
68
+ ): Promise<Response> {
69
+ const isWSRequest = request.headers.get("Upgrade") === "websocket";
70
+
71
+ if (!isWSRequest) {
72
+ return new Response("Expected websocket", { status: 400 });
73
+ }
74
+
75
+ const { 0: client, 1: server } = new WebSocketPair();
76
+
77
+ const token = new URL(request.url).searchParams.get("token");
78
+
79
+ await this._io.register(server, { token });
80
+
81
+ return new Response(null, { status: 101, webSocket: client });
82
+ }
83
+ };
84
+
85
+ const getDurableObjectNamespace = (env: TEnv): DurableObjectNamespace => {
86
+ const namespace = env[binding];
87
+
88
+ if (!namespace) {
89
+ throw new Error(`Could not find DurableObject binding: ${binding}`);
90
+ }
91
+
92
+ return namespace;
93
+ };
94
+
95
+ const authHandler: PluvHandlerFetch<TEnv> = async (request, env) => {
96
+ if (!authorize) return null;
97
+
98
+ const { pathname, searchParams } = new URL(request.url);
99
+ const matcher = match<{}>(`${endpoint}/authorize`);
100
+ const matched = matcher(pathname);
101
+
102
+ if (!matched) return null;
103
+
104
+ const roomId = searchParams.get("room");
105
+
106
+ if (!roomId) {
107
+ return new Response("Not found", {
108
+ headers: { "Content-Type": "text/plain" },
109
+ status: 404,
110
+ });
111
+ }
112
+
113
+ try {
114
+ const user = await authorize({ request, roomId });
115
+
116
+ if (!user) throw new Error();
117
+
118
+ const namespace = getDurableObjectNamespace(env);
119
+ const durableObjectId = namespace.idFromName(roomId);
120
+
121
+ const token = await io.createToken({
122
+ room: durableObjectId.toString(),
123
+ user,
124
+ });
125
+
126
+ return new Response(token, {
127
+ headers: { "Content-Type": "text/plain" },
128
+ status: 200,
129
+ });
130
+ } catch (err) {
131
+ return new Response(
132
+ err instanceof Error ? err.message : "Unauthorized",
133
+ {
134
+ headers: { "Content-Type": "text/plain" },
135
+ status: 403,
136
+ }
137
+ );
138
+ }
139
+ };
140
+
141
+ const roomHandler: PluvHandlerFetch<TEnv> = async (request, env) => {
142
+ const { pathname } = new URL(request.url);
143
+ const matcher = match<{ roomId: string }>(`${endpoint}/room/:roomId`);
144
+ const matched = matcher(pathname);
145
+
146
+ if (!matched) return null;
147
+
148
+ const { roomId } = matched.params;
149
+
150
+ if (!roomId) {
151
+ return new Response("Not found", {
152
+ headers: { "Content-Type": "text/plain" },
153
+ status: 404,
154
+ });
155
+ }
156
+
157
+ const namespace = getDurableObjectNamespace(env);
158
+ const durableObjectId = namespace.idFromName(roomId);
159
+ const room = namespace.get(durableObjectId);
160
+
161
+ return room.fetch(request);
162
+ };
163
+
164
+ const fetch: PluvHandlerFetch<TEnv> = async (request, env) => {
165
+ return [authHandler, roomHandler].reduce((promise, current) => {
166
+ return promise.then((value) => value ?? current(request, env));
167
+ }, Promise.resolve<Response | null>(null));
168
+ };
169
+
170
+ const handler: ExportedHandler<TEnv> = {
171
+ fetch: async (request, env) => {
172
+ const response =
173
+ (await fetch(request, env)) ??
174
+ new Response("Not Found", {
175
+ headers: { "Content-Type": "text/plain" },
176
+ status: 404,
177
+ });
178
+
179
+ return modify?.(request, response, env) ?? response;
180
+ },
181
+ };
182
+
183
+ return { fetch, DurableObject, handler };
184
+ };
package/src/index.ts CHANGED
@@ -1 +1,9 @@
1
+ export type {
2
+ AuthorizeFunction,
3
+ AuthorizeFunctionContext,
4
+ CreatePluvHandlerConfig,
5
+ CreatePluvHandlerResult,
6
+ PluvHandlerFetch,
7
+ } from "./createPluvHandler";
8
+ export { createPluvHandler } from "./createPluvHandler";
1
9
  export { platformCloudflare } from "./platformCloudflare";