@pluv/platform-node 0.0.0-canary-20261027022227
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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/index.d.mts +71 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +204 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -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-node`
|
|
2
|
+
|
|
3
|
+
> Enables [@pluv/io](https://www.npmjs.com/package/@pluv/io) to run on [Node.js](https://nodejs.org/).
|
|
4
|
+
|
|
5
|
+
**👉 See full documentation on [docs.pluv.io](https://docs.pluv.io/docs). 👈**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# npm
|
|
11
|
+
npm install @pluv/platform-node ws @types/ws
|
|
12
|
+
|
|
13
|
+
# yarn
|
|
14
|
+
yarn add @pluv/platform-node ws @types/ws
|
|
15
|
+
|
|
16
|
+
# pnpm
|
|
17
|
+
pnpm add @pluv/platform-node ws @types/ws
|
|
18
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AbstractEventMap, AbstractListener, AbstractPersistence, AbstractPlatform, AbstractPlatformConfig, AbstractWebSocket, AbstractWebSocketConfig, ConvertWebSocketConfig, WebSocketSerializedState, WebSocketSession } from "@pluv/io";
|
|
2
|
+
import { WebSocket, WebSocket as WebSocket$1 } from "ws";
|
|
3
|
+
import { BaseUser, Id, Json, JsonObject } from "@pluv/types";
|
|
4
|
+
import { IncomingMessage } from "node:http";
|
|
5
|
+
declare const identity: <T extends unknown>(x: T) => T;
|
|
6
|
+
type ToRequestOptions = {
|
|
7
|
+
origin?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const toRequest: (request: Request | IncomingMessage, options?: ToRequestOptions) => Request;
|
|
10
|
+
type InferCallback<TMeta extends Record<string, Json> = {}> = (i: typeof identity) => {
|
|
11
|
+
meta?: (io: TMeta) => TMeta;
|
|
12
|
+
};
|
|
13
|
+
export declare const infer: <TMeta extends Record<string, Json> = {}>(callback: InferCallback<TMeta>) => InferCallback<TMeta>;
|
|
14
|
+
type NodeWebSocketConfig = AbstractWebSocketConfig;
|
|
15
|
+
declare class NodeWebSocket extends AbstractWebSocket<WebSocket$1> {
|
|
16
|
+
private _sessionId;
|
|
17
|
+
private _state;
|
|
18
|
+
private _user;
|
|
19
|
+
set presence(presence: JsonObject | null);
|
|
20
|
+
get readyState(): 0 | 1 | 2 | 3;
|
|
21
|
+
get session(): WebSocketSession<any>;
|
|
22
|
+
get sessionId(): string;
|
|
23
|
+
get state(): WebSocketSerializedState;
|
|
24
|
+
set state(state: WebSocketSerializedState);
|
|
25
|
+
get user(): BaseUser | null;
|
|
26
|
+
set user(user: BaseUser);
|
|
27
|
+
constructor(webSocket: WebSocket$1, config: NodeWebSocketConfig);
|
|
28
|
+
addEventListener<TType extends keyof AbstractEventMap>(type: TType, handler: AbstractListener<TType>): void;
|
|
29
|
+
close(code?: number, reason?: string): void;
|
|
30
|
+
send(message: string | ArrayBuffer | ArrayBufferView): void;
|
|
31
|
+
terminate(): void;
|
|
32
|
+
}
|
|
33
|
+
type NodeRegisterInput = {
|
|
34
|
+
request: Request | IncomingMessage;
|
|
35
|
+
};
|
|
36
|
+
type NodeAuthorizeContext = {
|
|
37
|
+
request: Request;
|
|
38
|
+
};
|
|
39
|
+
type NodePlatformRoomContext<TMeta extends Record<string, Json>> = keyof TMeta extends never ? {
|
|
40
|
+
meta?: undefined;
|
|
41
|
+
} : {
|
|
42
|
+
meta: TMeta;
|
|
43
|
+
};
|
|
44
|
+
type NodePlatformConfig<TMeta extends Record<string, Json>> = {
|
|
45
|
+
origin?: string;
|
|
46
|
+
persistence?: AbstractPersistence;
|
|
47
|
+
roomContext?: NodePlatformRoomContext<TMeta>;
|
|
48
|
+
};
|
|
49
|
+
declare class NodePlatform<TMeta extends Record<string, Json> = {}> extends AbstractPlatform<NodeWebSocket, NodeRegisterInput, NodePlatformRoomContext<TMeta>> {
|
|
50
|
+
readonly id: `${string}-${string}-${string}-${string}-${string}`;
|
|
51
|
+
readonly _name = "platformNode";
|
|
52
|
+
readonly origin: string | undefined;
|
|
53
|
+
constructor(config?: NodePlatformConfig<TMeta>);
|
|
54
|
+
acceptWebSocket(webSocket: NodeWebSocket): Promise<void>;
|
|
55
|
+
convertWebSocket(webSocket: WebSocket$1, config: ConvertWebSocketConfig): NodeWebSocket;
|
|
56
|
+
getLastPing(webSocket: NodeWebSocket): number | null;
|
|
57
|
+
getSerializedState(webSocket: WebSocket$1): WebSocketSerializedState | null;
|
|
58
|
+
getSessionId(webSocket: WebSocket$1): string | null;
|
|
59
|
+
getWebSockets(): readonly WebSocket$1[];
|
|
60
|
+
initialize(config: AbstractPlatformConfig<NodePlatformRoomContext<TMeta>>): this;
|
|
61
|
+
normalizeInitContext(initContext: NodeRegisterInput): NodeRegisterInput;
|
|
62
|
+
parseData(data: string | ArrayBuffer): Record<string, any>;
|
|
63
|
+
randomUUID(): string;
|
|
64
|
+
setSerializedState(webSocket: NodeWebSocket, state: WebSocketSerializedState): WebSocketSerializedState;
|
|
65
|
+
}
|
|
66
|
+
type PlatformNodeParams<TMeta extends Record<string, Json> = {}> = Id<NodePlatformConfig<TMeta> & {
|
|
67
|
+
types?: InferCallback<TMeta>;
|
|
68
|
+
}>;
|
|
69
|
+
export declare const platformNode: <TMeta extends Record<string, Json> = {}>(config?: PlatformNodeParams<TMeta>) => (() => NodePlatform<TMeta>);
|
|
70
|
+
export type { InferCallback, NodeAuthorizeContext, NodePlatform, NodePlatformConfig, NodeRegisterInput, PlatformNodeParams, ToRequestOptions, WebSocket };
|
|
71
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["WebSocket","WebSocket"],"sources":["../src/utils/identity.ts","../src/utils/toRequest.ts","../src/infer.ts","../src/NodeWebSocket.ts","../src/types.ts","../src/NodePlatform.ts","../src/platformNode.ts"],"mappings":";;;;cAAa,WAAY,mBAAiB,GAAK,MAAI;KCwCvC;EACR;;qBAGS,YAAS,SACT,UAAU,iBAAe,UACzB,qBACV;KC5CS,cAAc,cAAc,eAAe,eAAe,UAAU;EAC5E,QAAQ,IAAI,UAAU;;qBAGb,QAAS,cAAc,eAAe,YAAU,UAAY,cAAc,WAAM,cAAA;KCcjF,sBAAsB;cAErB,sBAAsB,kBAAkBA;UACzC;UACA;UACA;MAEG,SAAS,UAAU;MAInB;MAIA,WAAW;MAeX;MAUA,SAAS;MAIT,MAAM,OAAO;MAIb,QAAQ;MAIR,KAAK,MAAM;EAItB,YAAY,WAAWA,aAAW,QAAQ;EAkBnC,iBAAiB,oBAAoB,kBACxC,MAAM,OACN,SAAS,iBAAiB;EA+BvB,MAAM,eAAe;EAUrB,KAAK,kBAAkB,cAAc;EAQrC;;KChJC;EAAsB,SAAS,UAAU;;KAEzC;EAAyB,SAAS;;KCWlC,wBAAwB,cAAc,eAAe,eAAe;EACxE;;EACA,MAAM;;KAEF,mBAAmB,cAAc,eAAe;EACxD;EACA,cAAc;EACd,cAAc,wBAAwB;;cAG7B,aAAa,cAAc,eAAe,oBAAoB,iBACvE,eACA,mBACA,wBAAwB;WAER;WACA;WACA;EAEhB,YAAY,SAAQ,mBAAmB;EAWhC,gBAAgB,WAAW,gBAAgB;EAI3C,iBAAiB,WAAWC,aAAW,QAAQ,yBAAyB;EAUxE,YAAY,WAAW;EAIvB,mBAAmB,WAAWA,cAAY;EAI1C,aAAa,WAAWA;EAIxB,0BAA0BA;EAI1B,WAAW,QAAQ,uBAAuB,wBAAwB;EAQlE,qBAAqB,aAAa,oBAAoB;EAMtD,UAAU,eAAe,cAAc;EAQvC;EAIA,mBACH,WAAW,eACX,OAAO,2BACR;;KCnGK,mBAAmB,cAAc,eAAe,cAAc,GACtE,mBAAmB;EACf,QAAQ,cAAc;;qBAIjB,eAAgB,cAAc,eAAe,YAAU,SACxD,mBAAmB,kBACrB,aAAa"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { AbstractPlatform, AbstractWebSocket } from "@pluv/io";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { TextDecoder } from "node:util";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
// src/infer.ts
|
|
6
|
+
const infer = (callback) => callback;
|
|
7
|
+
// src/NodeWebSocket.ts
|
|
8
|
+
var NodeWebSocket = class extends AbstractWebSocket {
|
|
9
|
+
_sessionId = null;
|
|
10
|
+
_state;
|
|
11
|
+
_user = null;
|
|
12
|
+
set presence(presence) {
|
|
13
|
+
this._state.presence = presence;
|
|
14
|
+
}
|
|
15
|
+
get readyState() {
|
|
16
|
+
return this.webSocket.readyState;
|
|
17
|
+
}
|
|
18
|
+
get session() {
|
|
19
|
+
const user = this._user;
|
|
20
|
+
if (!user) throw new Error("WebSocket is not authorized");
|
|
21
|
+
return {
|
|
22
|
+
...this._state,
|
|
23
|
+
id: this.sessionId,
|
|
24
|
+
user,
|
|
25
|
+
webSocket: this
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
get sessionId() {
|
|
29
|
+
if (this._sessionId) return this._sessionId;
|
|
30
|
+
const sessionId = this._sessionId ?? `p_${crypto.randomUUID()}`;
|
|
31
|
+
this._sessionId = sessionId;
|
|
32
|
+
return sessionId;
|
|
33
|
+
}
|
|
34
|
+
get state() {
|
|
35
|
+
return this._state;
|
|
36
|
+
}
|
|
37
|
+
set state(state) {
|
|
38
|
+
this._state = state;
|
|
39
|
+
}
|
|
40
|
+
get user() {
|
|
41
|
+
return this._user;
|
|
42
|
+
}
|
|
43
|
+
set user(user) {
|
|
44
|
+
this._user = user;
|
|
45
|
+
}
|
|
46
|
+
constructor(webSocket, config) {
|
|
47
|
+
const { room } = config;
|
|
48
|
+
super(webSocket, config);
|
|
49
|
+
this._state = {
|
|
50
|
+
kind: "user",
|
|
51
|
+
operator: null,
|
|
52
|
+
presence: null,
|
|
53
|
+
quit: false,
|
|
54
|
+
room,
|
|
55
|
+
seq: { presence: null },
|
|
56
|
+
timers: { ping: (/* @__PURE__ */ new Date()).getTime() }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
addEventListener(type, handler) {
|
|
60
|
+
switch (type) {
|
|
61
|
+
case "close":
|
|
62
|
+
this.webSocket.on("close", async (code, buffer) => {
|
|
63
|
+
const reason = buffer.toString("utf-8");
|
|
64
|
+
await Promise.resolve(handler({
|
|
65
|
+
code,
|
|
66
|
+
reason
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
case "error":
|
|
71
|
+
this.webSocket.on("error", async (error) => {
|
|
72
|
+
const message = error.message;
|
|
73
|
+
await Promise.resolve(handler({
|
|
74
|
+
error,
|
|
75
|
+
message
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
case "message":
|
|
80
|
+
this.webSocket.on("message", async (data) => {
|
|
81
|
+
await Promise.resolve(handler({ data }));
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
close(code, reason) {
|
|
87
|
+
if (![this.CONNECTING, this.OPEN].some((readyState) => readyState === this.readyState)) return;
|
|
88
|
+
this.webSocket.close(code, reason);
|
|
89
|
+
}
|
|
90
|
+
send(message) {
|
|
91
|
+
if (this.readyState !== this.OPEN) return;
|
|
92
|
+
this.webSocket.send(message, (error) => {
|
|
93
|
+
if (error) throw error;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
terminate() {
|
|
97
|
+
this.webSocket.terminate();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
// src/utils/toRequest.ts
|
|
101
|
+
const getHeader = (incoming, name) => {
|
|
102
|
+
const value = incoming.headers[name];
|
|
103
|
+
if (Array.isArray(value)) return value[0];
|
|
104
|
+
return value;
|
|
105
|
+
};
|
|
106
|
+
const inferOrigin = (incoming) => {
|
|
107
|
+
const host = getHeader(incoming, "x-forwarded-host") ?? getHeader(incoming, "host") ?? "localhost";
|
|
108
|
+
return `${getHeader(incoming, "x-forwarded-proto") ?? (incoming.socket.encrypted ? "https" : "http")}://${host}`;
|
|
109
|
+
};
|
|
110
|
+
const toHeaders = (incoming) => {
|
|
111
|
+
const headers = new Headers();
|
|
112
|
+
for (const [key, value] of Object.entries(incoming.headers)) {
|
|
113
|
+
if (value == null) continue;
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
for (const item of value) headers.append(key, item);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
headers.append(key, value);
|
|
119
|
+
}
|
|
120
|
+
return headers;
|
|
121
|
+
};
|
|
122
|
+
const toRequest = (request, options = {}) => {
|
|
123
|
+
if (request instanceof Request) return request;
|
|
124
|
+
const origin = options.origin ?? inferOrigin(request);
|
|
125
|
+
const url = new URL(request.url ?? "/", origin);
|
|
126
|
+
const method = request.method ?? "GET";
|
|
127
|
+
const headers = toHeaders(request);
|
|
128
|
+
if (method === "GET" || method === "HEAD") return new Request(url, {
|
|
129
|
+
headers,
|
|
130
|
+
method
|
|
131
|
+
});
|
|
132
|
+
return new Request(url, {
|
|
133
|
+
body: Readable.toWeb(request),
|
|
134
|
+
duplex: "half",
|
|
135
|
+
headers,
|
|
136
|
+
method
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
// src/NodePlatform.ts
|
|
140
|
+
var NodePlatform = class NodePlatform extends AbstractPlatform {
|
|
141
|
+
id = crypto.randomUUID();
|
|
142
|
+
_name = "platformNode";
|
|
143
|
+
origin;
|
|
144
|
+
constructor(config = {}) {
|
|
145
|
+
const { origin, roomContext, persistence } = config;
|
|
146
|
+
super({
|
|
147
|
+
roomContext,
|
|
148
|
+
...persistence ? { persistence } : {}
|
|
149
|
+
});
|
|
150
|
+
this.origin = origin;
|
|
151
|
+
}
|
|
152
|
+
acceptWebSocket(webSocket) {
|
|
153
|
+
return Promise.resolve(void 0);
|
|
154
|
+
}
|
|
155
|
+
convertWebSocket(webSocket, config) {
|
|
156
|
+
const { room } = config;
|
|
157
|
+
return new NodeWebSocket(webSocket, {
|
|
158
|
+
persistence: this.persistence,
|
|
159
|
+
platform: this,
|
|
160
|
+
room
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
getLastPing(webSocket) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
getSerializedState(webSocket) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
getSessionId(webSocket) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
getWebSockets() {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
initialize(config) {
|
|
176
|
+
return new NodePlatform({
|
|
177
|
+
origin: this.origin,
|
|
178
|
+
persistence: this.persistence.initialize(config.roomContext),
|
|
179
|
+
roomContext: config.roomContext
|
|
180
|
+
})._initialize();
|
|
181
|
+
}
|
|
182
|
+
normalizeInitContext(initContext) {
|
|
183
|
+
return { request: toRequest(initContext.request, { origin: this.origin }) };
|
|
184
|
+
}
|
|
185
|
+
parseData(data) {
|
|
186
|
+
if (typeof data === "string") return JSON.parse(data);
|
|
187
|
+
const decoder = new TextDecoder("utf8");
|
|
188
|
+
return JSON.parse(decoder.decode(data));
|
|
189
|
+
}
|
|
190
|
+
randomUUID() {
|
|
191
|
+
return crypto.randomUUID();
|
|
192
|
+
}
|
|
193
|
+
setSerializedState(webSocket, state) {
|
|
194
|
+
webSocket.state = state;
|
|
195
|
+
return webSocket.state;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
// src/platformNode.ts
|
|
199
|
+
const platformNode = (config = {}) => {
|
|
200
|
+
return () => new NodePlatform(config);
|
|
201
|
+
};
|
|
202
|
+
export { infer, platformNode, toRequest };
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/infer.ts","../src/NodeWebSocket.ts","../src/utils/toRequest.ts","../src/NodePlatform.ts","../src/platformNode.ts"],"sourcesContent":["import type { Json } from \"@pluv/types\";\nimport type { identity } from \"./utils\";\n\nexport type InferCallback<TMeta extends Record<string, Json> = {}> = (i: typeof identity) => {\n meta?: (io: TMeta) => TMeta;\n};\n\nexport const infer = <TMeta extends Record<string, Json> = {}>(callback: InferCallback<TMeta>) =>\n callback;\n","import type {\n AbstractCloseEvent,\n AbstractErrorEvent,\n AbstractEventMap,\n AbstractListener,\n AbstractMessageEvent,\n AbstractWebSocketConfig,\n WebSocketSerializedState,\n WebSocketSession,\n} from \"@pluv/io\";\nimport { AbstractWebSocket } from \"@pluv/io\";\nimport type { BaseUser, JsonObject } from \"@pluv/types\";\nimport crypto from \"node:crypto\";\nimport type { WebSocket } from \"ws\";\n\nexport interface NodeWebSocketEventMap {\n close: AbstractCloseEvent;\n message: AbstractMessageEvent;\n error: AbstractErrorEvent;\n}\n\nexport type NodeWebSocketConfig = AbstractWebSocketConfig;\n\nexport class NodeWebSocket extends AbstractWebSocket<WebSocket> {\n private _sessionId: string | null = null;\n private _state: WebSocketSerializedState;\n private _user: BaseUser | null = null;\n\n public set presence(presence: JsonObject | null) {\n this._state.presence = presence;\n }\n\n public get readyState(): 0 | 1 | 2 | 3 {\n return this.webSocket.readyState;\n }\n\n public get session(): WebSocketSession<any> {\n const user = this._user;\n\n if (!user) {\n throw new Error(\"WebSocket is not authorized\");\n }\n\n return {\n ...this._state,\n id: this.sessionId,\n user,\n webSocket: this,\n };\n }\n\n public get sessionId(): string {\n if (this._sessionId) return this._sessionId;\n\n const sessionId = this._sessionId ?? `p_${crypto.randomUUID()}`;\n\n this._sessionId = sessionId;\n\n return sessionId;\n }\n\n public get state(): WebSocketSerializedState {\n return this._state;\n }\n\n public set state(state: WebSocketSerializedState) {\n this._state = state;\n }\n\n public get user(): BaseUser | null {\n return this._user;\n }\n\n public set user(user: BaseUser) {\n this._user = user;\n }\n\n constructor(webSocket: WebSocket, config: NodeWebSocketConfig) {\n const { room } = config;\n\n super(webSocket, config);\n\n this._state = {\n kind: \"user\",\n operator: null,\n presence: null,\n quit: false,\n room,\n seq: { presence: null },\n timers: {\n ping: new Date().getTime(),\n },\n };\n }\n\n public addEventListener<TType extends keyof AbstractEventMap>(\n type: TType,\n handler: AbstractListener<TType>,\n ): void {\n switch (type) {\n case \"close\":\n this.webSocket.on(\"close\", async (code, buffer) => {\n const reason = buffer.toString(\"utf-8\");\n\n await Promise.resolve((handler as AbstractListener<\"close\">)({ code, reason }));\n });\n\n return;\n case \"error\":\n this.webSocket.on(\"error\", async (error) => {\n const message = error.message;\n\n await Promise.resolve(\n (handler as AbstractListener<\"error\">)({ error, message }),\n );\n });\n\n return;\n case \"message\":\n this.webSocket.on(\"message\", async (data) => {\n await Promise.resolve((handler as AbstractListener<\"message\">)({ data }));\n });\n\n return;\n default:\n }\n }\n\n public close(code?: number, reason?: string): void {\n const canClose = [this.CONNECTING, this.OPEN].some(\n (readyState) => readyState === this.readyState,\n );\n\n if (!canClose) return;\n\n this.webSocket.close(code, reason);\n }\n\n public send(message: string | ArrayBuffer | ArrayBufferView): void {\n if (this.readyState !== this.OPEN) return;\n\n this.webSocket.send(message, (error) => {\n if (error) throw error;\n });\n }\n\n public terminate(): void {\n this.webSocket.terminate();\n }\n}\n","import type { IncomingMessage } from \"node:http\";\nimport { Readable } from \"node:stream\";\n\nconst getHeader = (incoming: IncomingMessage, name: string): string | undefined => {\n const value = incoming.headers[name];\n\n if (Array.isArray(value)) return value[0];\n\n return value;\n};\n\nconst inferOrigin = (incoming: IncomingMessage): string => {\n const host =\n getHeader(incoming, \"x-forwarded-host\") ?? getHeader(incoming, \"host\") ?? \"localhost\";\n const forwardedProto = getHeader(incoming, \"x-forwarded-proto\");\n const protocol =\n forwardedProto ??\n ((incoming.socket as { encrypted?: boolean }).encrypted ? \"https\" : \"http\");\n\n return `${protocol}://${host}`;\n};\n\nconst toHeaders = (incoming: IncomingMessage): Headers => {\n const headers = new Headers();\n\n for (const [key, value] of Object.entries(incoming.headers)) {\n if (value == null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) headers.append(key, item);\n\n continue;\n }\n\n headers.append(key, value);\n }\n\n return headers;\n};\n\nexport type ToRequestOptions = {\n origin?: string;\n};\n\nexport const toRequest = (\n request: Request | IncomingMessage,\n options: ToRequestOptions = {},\n): Request => {\n if (request instanceof Request) return request;\n\n const origin = options.origin ?? inferOrigin(request);\n const url = new URL(request.url ?? \"/\", origin);\n const method = request.method ?? \"GET\";\n const headers = toHeaders(request);\n\n if (method === \"GET\" || method === \"HEAD\") {\n return new Request(url, {\n headers,\n method,\n });\n }\n\n return new Request(url, {\n body: Readable.toWeb(request) as ReadableStream,\n duplex: \"half\",\n headers,\n method,\n });\n};\n","import type {\n AbstractPersistence,\n AbstractPlatformConfig,\n ConvertWebSocketConfig,\n WebSocketSerializedState,\n} from \"@pluv/io\";\nimport { AbstractPlatform } from \"@pluv/io\";\nimport type { Json } from \"@pluv/types\";\nimport crypto from \"node:crypto\";\nimport { TextDecoder } from \"node:util\";\nimport type { WebSocket } from \"ws\";\nimport { NodeWebSocket } from \"./NodeWebSocket\";\nimport type { NodeRegisterInput } from \"./types\";\nimport { toRequest } from \"./utils/toRequest\";\n\nexport type NodePlatformRoomContext<TMeta extends Record<string, Json>> = keyof TMeta extends never\n ? { meta?: undefined }\n : { meta: TMeta };\n\nexport type NodePlatformConfig<TMeta extends Record<string, Json>> = {\n origin?: string;\n persistence?: AbstractPersistence;\n roomContext?: NodePlatformRoomContext<TMeta>;\n};\n\nexport class NodePlatform<TMeta extends Record<string, Json> = {}> extends AbstractPlatform<\n NodeWebSocket,\n NodeRegisterInput,\n NodePlatformRoomContext<TMeta>\n> {\n public readonly id = crypto.randomUUID();\n public readonly _name = \"platformNode\";\n public readonly origin: string | undefined;\n\n constructor(config: NodePlatformConfig<TMeta> = {}) {\n const { origin, roomContext, persistence } = config;\n\n super({\n roomContext,\n ...(persistence ? { persistence } : {}),\n });\n\n this.origin = origin;\n }\n\n public acceptWebSocket(webSocket: NodeWebSocket): Promise<void> {\n return Promise.resolve(undefined);\n }\n\n public convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): NodeWebSocket {\n const { room } = config;\n\n return new NodeWebSocket(webSocket, {\n persistence: this.persistence,\n platform: this,\n room,\n });\n }\n\n public getLastPing(webSocket: NodeWebSocket): number | null {\n return null;\n }\n\n public getSerializedState(webSocket: WebSocket): WebSocketSerializedState | null {\n return null;\n }\n\n public getSessionId(webSocket: WebSocket): string | null {\n return null;\n }\n\n public getWebSockets(): readonly WebSocket[] {\n return [];\n }\n\n public initialize(config: AbstractPlatformConfig<NodePlatformRoomContext<TMeta>>): this {\n return new NodePlatform({\n origin: this.origin,\n persistence: this.persistence.initialize(config.roomContext),\n roomContext: config.roomContext,\n })._initialize() as this;\n }\n\n public normalizeInitContext(initContext: NodeRegisterInput): NodeRegisterInput {\n return {\n request: toRequest(initContext.request, { origin: this.origin }),\n };\n }\n\n public parseData(data: string | ArrayBuffer): Record<string, any> {\n if (typeof data === \"string\") return JSON.parse(data);\n\n const decoder = new TextDecoder(\"utf8\");\n\n return JSON.parse(decoder.decode(data));\n }\n\n public randomUUID(): string {\n return crypto.randomUUID();\n }\n\n public setSerializedState(\n webSocket: NodeWebSocket,\n state: WebSocketSerializedState,\n ): WebSocketSerializedState {\n webSocket.state = state;\n\n return webSocket.state;\n }\n}\n","import type { Id, Json } from \"@pluv/types\";\nimport type { NodePlatformConfig } from \"./NodePlatform\";\nimport { NodePlatform } from \"./NodePlatform\";\nimport type { InferCallback } from \"./infer\";\n\nexport type PlatformNodeParams<TMeta extends Record<string, Json> = {}> = Id<\n NodePlatformConfig<TMeta> & {\n types?: InferCallback<TMeta>;\n }\n>;\n\nexport const platformNode = <TMeta extends Record<string, Json> = {}>(\n config: PlatformNodeParams<TMeta> = {} as PlatformNodeParams<TMeta>,\n): (() => NodePlatform<TMeta>) => {\n return () => new NodePlatform<TMeta>(config);\n};\n"],"mappings":";;;;;AAOA,MAAa,SAAkD,aAC3D;;ACeJ,IAAa,gBAAb,cAAmC,kBAA6B;CAC5D,aAAoC;CACpC;CACA,QAAiC;CAEjC,IAAW,SAAS,UAA6B;EAC7C,KAAK,OAAO,WAAW;CAC3B;CAEA,IAAW,aAA4B;EACnC,OAAO,KAAK,UAAU;CAC1B;CAEA,IAAW,UAAiC;EACxC,MAAM,OAAO,KAAK;EAElB,IAAI,CAAC,MACD,MAAM,IAAI,MAAM,6BAA6B;EAGjD,OAAO;GACH,GAAG,KAAK;GACR,IAAI,KAAK;GACT;GACA,WAAW;EACf;CACJ;CAEA,IAAW,YAAoB;EAC3B,IAAI,KAAK,YAAY,OAAO,KAAK;EAEjC,MAAM,YAAY,KAAK,cAAc,KAAK,OAAO,WAAW;EAE5D,KAAK,aAAa;EAElB,OAAO;CACX;CAEA,IAAW,QAAkC;EACzC,OAAO,KAAK;CAChB;CAEA,IAAW,MAAM,OAAiC;EAC9C,KAAK,SAAS;CAClB;CAEA,IAAW,OAAwB;EAC/B,OAAO,KAAK;CAChB;CAEA,IAAW,KAAK,MAAgB;EAC5B,KAAK,QAAQ;CACjB;CAEA,YAAY,WAAsB,QAA6B;EAC3D,MAAM,EAAE,SAAS;EAEjB,MAAM,WAAW,MAAM;EAEvB,KAAK,SAAS;GACV,MAAM;GACN,UAAU;GACV,UAAU;GACV,MAAM;GACN;GACA,KAAK,EAAE,UAAU,KAAK;GACtB,QAAQ,EACJ,uBAAM,IAAI,KAAK,EAAA,CAAE,QAAQ,EAC7B;EACJ;CACJ;CAEA,iBACI,MACA,SACI;EACJ,QAAQ,MAAR;GACI,KAAK;IACD,KAAK,UAAU,GAAG,SAAS,OAAO,MAAM,WAAW;KAC/C,MAAM,SAAS,OAAO,SAAS,OAAO;KAEtC,MAAM,QAAQ,QAAS,QAAsC;MAAE;MAAM;KAAO,CAAC,CAAC;IAClF,CAAC;IAED;GACJ,KAAK;IACD,KAAK,UAAU,GAAG,SAAS,OAAO,UAAU;KACxC,MAAM,UAAU,MAAM;KAEtB,MAAM,QAAQ,QACT,QAAsC;MAAE;MAAO;KAAQ,CAAC,CAC7D;IACJ,CAAC;IAED;GACJ,KAAK;IACD,KAAK,UAAU,GAAG,WAAW,OAAO,SAAS;KACzC,MAAM,QAAQ,QAAS,QAAwC,EAAE,KAAK,CAAC,CAAC;IAC5E,CAAC;IAED;EAER;CACJ;CAEA,MAAa,MAAe,QAAuB;EAK/C,IAAI,CAJa,CAAC,KAAK,YAAY,KAAK,IAAI,CAAC,CAAC,MACzC,eAAe,eAAe,KAAK,UAG5B,GAAG;EAEf,KAAK,UAAU,MAAM,MAAM,MAAM;CACrC;CAEA,KAAY,SAAuD;EAC/D,IAAI,KAAK,eAAe,KAAK,MAAM;EAEnC,KAAK,UAAU,KAAK,UAAU,UAAU;GACpC,IAAI,OAAO,MAAM;EACrB,CAAC;CACL;CAEA,YAAyB;EACrB,KAAK,UAAU,UAAU;CAC7B;AACJ;;AClJA,MAAM,aAAa,UAA2B,SAAqC;CAC/E,MAAM,QAAQ,SAAS,QAAQ;CAE/B,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM;CAEvC,OAAO;AACX;AAEA,MAAM,eAAe,aAAsC;CACvD,MAAM,OACF,UAAU,UAAU,kBAAkB,KAAK,UAAU,UAAU,MAAM,KAAK;CAM9E,OAAO,GALgB,UAAU,UAAU,mBAE1B,MACX,SAAS,OAAmC,YAAY,UAAU,QAErD,KAAK;AAC5B;AAEA,MAAM,aAAa,aAAuC;CACtD,MAAM,UAAU,IAAI,QAAQ;CAE5B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,OAAO,GAAG;EACzD,IAAI,SAAS,MAAM;EAEnB,IAAI,MAAM,QAAQ,KAAK,GAAG;GACtB,KAAK,MAAM,QAAQ,OAAO,QAAQ,OAAO,KAAK,IAAI;GAElD;EACJ;EAEA,QAAQ,OAAO,KAAK,KAAK;CAC7B;CAEA,OAAO;AACX;AAMA,MAAa,aACT,SACA,UAA4B,CAAC,MACnB;CACV,IAAI,mBAAmB,SAAS,OAAO;CAEvC,MAAM,SAAS,QAAQ,UAAU,YAAY,OAAO;CACpD,MAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,MAAM;CAC9C,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,UAAU,UAAU,OAAO;CAEjC,IAAI,WAAW,SAAS,WAAW,QAC/B,OAAO,IAAI,QAAQ,KAAK;EACpB;EACA;CACJ,CAAC;CAGL,OAAO,IAAI,QAAQ,KAAK;EACpB,MAAM,SAAS,MAAM,OAAO;EAC5B,QAAQ;EACR;EACA;CACJ,CAAC;AACL;;AC3CA,IAAa,eAAb,MAAa,qBAA8D,iBAIzE;CACE,KAAqB,OAAO,WAAW;CACvC,QAAwB;CACxB;CAEA,YAAY,SAAoC,CAAC,GAAG;EAChD,MAAM,EAAE,QAAQ,aAAa,gBAAgB;EAE7C,MAAM;GACF;GACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;EACzC,CAAC;EAED,KAAK,SAAS;CAClB;CAEA,gBAAuB,WAAyC;EAC5D,OAAO,QAAQ,QAAQ,KAAA,CAAS;CACpC;CAEA,iBAAwB,WAAsB,QAA+C;EACzF,MAAM,EAAE,SAAS;EAEjB,OAAO,IAAI,cAAc,WAAW;GAChC,aAAa,KAAK;GAClB,UAAU;GACV;EACJ,CAAC;CACL;CAEA,YAAmB,WAAyC;EACxD,OAAO;CACX;CAEA,mBAA0B,WAAuD;EAC7E,OAAO;CACX;CAEA,aAAoB,WAAqC;EACrD,OAAO;CACX;CAEA,gBAA6C;EACzC,OAAO,CAAC;CACZ;CAEA,WAAkB,QAAsE;EACpF,OAAO,IAAI,aAAa;GACpB,QAAQ,KAAK;GACb,aAAa,KAAK,YAAY,WAAW,OAAO,WAAW;GAC3D,aAAa,OAAO;EACxB,CAAC,CAAC,CAAC,YAAY;CACnB;CAEA,qBAA4B,aAAmD;EAC3E,OAAO,EACH,SAAS,UAAU,YAAY,SAAS,EAAE,QAAQ,KAAK,OAAO,CAAC,EACnE;CACJ;CAEA,UAAiB,MAAiD;EAC9D,IAAI,OAAO,SAAS,UAAU,OAAO,KAAK,MAAM,IAAI;EAEpD,MAAM,UAAU,IAAI,YAAY,MAAM;EAEtC,OAAO,KAAK,MAAM,QAAQ,OAAO,IAAI,CAAC;CAC1C;CAEA,aAA4B;EACxB,OAAO,OAAO,WAAW;CAC7B;CAEA,mBACI,WACA,OACwB;EACxB,UAAU,QAAQ;EAElB,OAAO,UAAU;CACrB;AACJ;;AClGA,MAAa,gBACT,SAAoC,CAAC,MACP;CAC9B,aAAa,IAAI,aAAoB,MAAM;AAC/C"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pluv/platform-node",
|
|
3
|
+
"version": "0.0.0-canary-20261027022227",
|
|
4
|
+
"description": "@pluv/io adapter for node.js",
|
|
5
|
+
"author": {
|
|
6
|
+
"email": "david@pluv.io",
|
|
7
|
+
"name": "i3dly",
|
|
8
|
+
"url": "https://github.com/i3dly"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"homepage": "https://github.com/pluv-io/pluv",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/pluv-io/pluv.git",
|
|
15
|
+
"directory": "packages/platform-node"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@pluv/crdt": "^0.0.0-canary-20261027022227",
|
|
27
|
+
"@pluv/io": "^0.0.0-canary-20261027022227",
|
|
28
|
+
"@pluv/types": "^0.0.0-canary-20261027022227",
|
|
29
|
+
"@types/node": "^26.6.5",
|
|
30
|
+
"@types/ws": "^8.18.2",
|
|
31
|
+
"path-to-regexp": "^9.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@pluv/tsconfig": "^0.0.0-canary-20261027022227",
|
|
35
|
+
"tsdown": "^0.23.2",
|
|
36
|
+
"typescript": "7.1.0-beta",
|
|
37
|
+
"ws": "^8.22.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"ws": "^8.0.0"
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": "./dist/index.mjs",
|
|
44
|
+
"./*": "./*"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"dev": "tsdown --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
|
51
|
+
}
|
|
52
|
+
}
|