@pluv/platform-cloudflare 0.1.18 → 0.2.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.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +28 -2
- package/dist/index.js +125 -0
- package/dist/index.mjs +125 -0
- package/package.json +5 -4
- package/src/createPluvHandler.ts +184 -0
- package/src/index.ts +8 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/platform-cloudflare@0.
|
|
2
|
+
> @pluv/platform-cloudflare@0.2.0 build /home/runner/work/pluv/pluv/packages/platform-cloudflare
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
11
|
+
[32mCJS[39m [1mdist/index.js [22m[32m6.66 KB[39m
|
|
12
|
+
[32mCJS[39m ⚡️ Build success in 72ms
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m5.53 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 81ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 5105ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.57 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @pluv/platform-cloudflare
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 643d5b5: added createPluvHandler to automatically set-up a pluv server with reasonable defaults
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- b85a232: bumped dependencies
|
|
12
|
+
- fde89cf: added defaults to the client to align it with createPluvClient by default
|
|
13
|
+
- Updated dependencies [b85a232]
|
|
14
|
+
- @pluv/io@0.4.1
|
|
15
|
+
- @pluv/types@0.2.0
|
|
16
|
+
|
|
3
17
|
## 0.1.18
|
|
4
18
|
|
|
5
19
|
### 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.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
-
"
|
|
20
|
+
"path-to-regexp": "^6.2.1",
|
|
21
|
+
"@pluv/io": "^0.4.1",
|
|
21
22
|
"@pluv/types": "^0.2.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@cloudflare/workers-types": "^4.
|
|
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.
|
|
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";
|