@polytric/openws-sdkgen 0.0.5 → 0.0.6
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/README.md +2 -2
- package/dist/main.cjs +13 -1
- package/dist/main.js +13 -1
- package/dist/plans/dotnet.cjs +1 -1
- package/dist/plans/dotnet.d.cts +2 -109
- package/dist/plans/dotnet.d.ts +2 -109
- package/dist/plans/typescript.cjs +569 -0
- package/dist/plans/typescript.d.cts +6 -0
- package/dist/plans/typescript.d.ts +6 -0
- package/dist/plans/typescript.js +532 -0
- package/dist/templates/typescript/package.json.ejs +41 -0
- package/dist/templates/typescript/src/core/index.ts.ejs +6 -0
- package/dist/templates/typescript/src/core/models/index.ts.ejs +3 -0
- package/dist/templates/typescript/src/core/models/model.ts.ejs +41 -0
- package/dist/templates/typescript/src/core/network.ts.ejs +517 -0
- package/dist/templates/typescript/src/core/roles/index.ts.ejs +3 -0
- package/dist/templates/typescript/src/core/roles/role.ts.ejs +104 -0
- package/dist/templates/typescript/src/index.ts.ejs +4 -0
- package/dist/templates/typescript/src/sdk/index.ts.ejs +3 -0
- package/dist/templates/typescript/src/sdk/role.ts.ejs +372 -0
- package/dist/templates/typescript/tsconfig.json.ejs +14 -0
- package/dist/templates/typescript/tsup.config.ts.ejs +10 -0
- package/dist/types-BdZPs123.d.cts +115 -0
- package/dist/types-BdZPs123.d.ts +115 -0
- package/package.json +10 -3
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
export const networkName = <%- JSON.stringify(ctx.networkName) %>
|
|
2
|
+
export const networkDescription = <%- JSON.stringify(ctx.description ?? '') %>
|
|
3
|
+
export const networkVersion = <%- JSON.stringify(ctx.version ?? '1.0.0') %>
|
|
4
|
+
|
|
5
|
+
export const endpoints = <%- JSON.stringify(
|
|
6
|
+
Object.fromEntries(ctx.allRoles.map(role => [role.roleName, role.endpoints])),
|
|
7
|
+
null,
|
|
8
|
+
4
|
|
9
|
+
) %>
|
|
10
|
+
|
|
11
|
+
<% if (ctx.isTypeScript) { -%>
|
|
12
|
+
export interface OpenWsEnvelope<Payload = unknown> {
|
|
13
|
+
fromRole: string
|
|
14
|
+
messageName: string
|
|
15
|
+
payload: Payload
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface OpenWsEndpoint {
|
|
19
|
+
scheme: string
|
|
20
|
+
host?: string
|
|
21
|
+
port?: number
|
|
22
|
+
path?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Unsubscribe = () => void
|
|
26
|
+
export type TransportEvent = 'message' | 'error'
|
|
27
|
+
export type TransportHandler = (data: unknown) => void | Promise<void>
|
|
28
|
+
|
|
29
|
+
export interface Transport {
|
|
30
|
+
send(data: string): void | Promise<void>
|
|
31
|
+
on?(event: TransportEvent, handler: TransportHandler): unknown
|
|
32
|
+
close?(): void
|
|
33
|
+
connect?(roleName: string, endpoint?: OpenWsEndpoint): void | Promise<void>
|
|
34
|
+
disconnect?(roleName: string): void | Promise<void>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BindTransportOptions {
|
|
38
|
+
closeOnError?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RawMessageHandler {
|
|
42
|
+
handleRawMessage(data: string): void | Promise<void>
|
|
43
|
+
messageError?(error: unknown): void | Promise<void>
|
|
44
|
+
socketError?(error: unknown): void | Promise<void>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function encodeEnvelope(envelope: OpenWsEnvelope): string {
|
|
48
|
+
return JSON.stringify(envelope)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function decodeEnvelope(data: string): OpenWsEnvelope {
|
|
52
|
+
return JSON.parse(data) as OpenWsEnvelope
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function canBindTransport(transport: Transport): boolean {
|
|
56
|
+
return typeof transport.on === 'function'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function bindTransport(
|
|
60
|
+
transport: Transport,
|
|
61
|
+
handler: RawMessageHandler,
|
|
62
|
+
options: BindTransportOptions = {}
|
|
63
|
+
): Unsubscribe {
|
|
64
|
+
const handleData = async (data: unknown) => {
|
|
65
|
+
try {
|
|
66
|
+
await handler.handleRawMessage(await normalizeMessageData(data))
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await handler.messageError?.(error)
|
|
69
|
+
if (options.closeOnError) {
|
|
70
|
+
transport.close?.()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const handleSocketError = async (error: unknown) => {
|
|
75
|
+
await handler.socketError?.(error)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const nodeHandler = (data: unknown, ..._args: unknown[]) => {
|
|
79
|
+
void handleData(data)
|
|
80
|
+
}
|
|
81
|
+
const nodeErrorHandler = (error: unknown, ..._args: unknown[]) => {
|
|
82
|
+
void handleSocketError(error)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof transport.on !== 'function') {
|
|
86
|
+
throw new Error('Transport must support on("message")')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const messageUnsubscribe = transport.on('message', nodeHandler)
|
|
90
|
+
const errorUnsubscribe = transport.on('error', nodeErrorHandler)
|
|
91
|
+
return () => {
|
|
92
|
+
if (typeof messageUnsubscribe === 'function') messageUnsubscribe()
|
|
93
|
+
if (typeof errorUnsubscribe === 'function') errorUnsubscribe()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class WsTransport implements Transport {
|
|
98
|
+
private socket?: unknown
|
|
99
|
+
private socketUnsubscribe?: Unsubscribe
|
|
100
|
+
private openPromise?: Promise<void>
|
|
101
|
+
private readonly listeners: Record<TransportEvent, Set<TransportHandler>> = {
|
|
102
|
+
message: new Set(),
|
|
103
|
+
error: new Set(),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
constructor(socket?: unknown) {
|
|
107
|
+
if (socket) {
|
|
108
|
+
this.bindSocket(socket)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async connect(_roleName: string, endpoint?: OpenWsEndpoint): Promise<void> {
|
|
113
|
+
if (!this.socket) {
|
|
114
|
+
if (!endpoint) {
|
|
115
|
+
throw new Error('Cannot connect without a WebSocket endpoint')
|
|
116
|
+
}
|
|
117
|
+
this.bindSocket(createWebSocket(endpoint))
|
|
118
|
+
}
|
|
119
|
+
await this.waitForOpen()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async disconnect(): Promise<void> {
|
|
123
|
+
this.close()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async send(data: string): Promise<void> {
|
|
127
|
+
await this.waitForOpen()
|
|
128
|
+
const socket = this.requireSocket() as { send?: (data: string) => void | Promise<void> }
|
|
129
|
+
if (typeof socket.send !== 'function') {
|
|
130
|
+
throw new Error('WebSocket object must support send(data)')
|
|
131
|
+
}
|
|
132
|
+
await socket.send(data)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
on(event: TransportEvent, handler: TransportHandler): Unsubscribe {
|
|
136
|
+
this.listeners[event].add(handler)
|
|
137
|
+
return () => {
|
|
138
|
+
this.listeners[event].delete(handler)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
close(): void {
|
|
143
|
+
const socket = this.socket as { close?: () => void } | undefined
|
|
144
|
+
socket?.close?.()
|
|
145
|
+
this.socketUnsubscribe?.()
|
|
146
|
+
this.socket = undefined
|
|
147
|
+
this.socketUnsubscribe = undefined
|
|
148
|
+
this.openPromise = undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private bindSocket(socket: unknown): void {
|
|
152
|
+
this.socketUnsubscribe?.()
|
|
153
|
+
this.socket = socket
|
|
154
|
+
this.openPromise = undefined
|
|
155
|
+
|
|
156
|
+
const unsubscribeMessage = addSocketListener(socket, 'message', data => {
|
|
157
|
+
void this.emit('message', getMessageEventData(data))
|
|
158
|
+
})
|
|
159
|
+
const unsubscribeError = addSocketListener(socket, 'error', error => {
|
|
160
|
+
void this.emit('error', error)
|
|
161
|
+
})
|
|
162
|
+
this.socketUnsubscribe = () => {
|
|
163
|
+
unsubscribeMessage()
|
|
164
|
+
unsubscribeError()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async emit(event: TransportEvent, data: unknown): Promise<void> {
|
|
169
|
+
for (const handler of this.listeners[event]) {
|
|
170
|
+
await handler(data)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private requireSocket(): unknown {
|
|
175
|
+
if (!this.socket) {
|
|
176
|
+
throw new Error('WebSocket is not connected')
|
|
177
|
+
}
|
|
178
|
+
return this.socket
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async waitForOpen(): Promise<void> {
|
|
182
|
+
const socket = this.requireSocket()
|
|
183
|
+
const readyState = getReadyState(socket)
|
|
184
|
+
if (readyState === undefined || readyState === 1) return
|
|
185
|
+
if (readyState === 2 || readyState === 3) {
|
|
186
|
+
throw new Error('WebSocket is closed')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.openPromise ??= new Promise<void>((resolve, reject) => {
|
|
190
|
+
let cleanup: Unsubscribe = () => {}
|
|
191
|
+
const unsubscribeOpen = addSocketListener(socket, 'open', () => {
|
|
192
|
+
cleanup()
|
|
193
|
+
resolve()
|
|
194
|
+
})
|
|
195
|
+
const unsubscribeError = addSocketListener(socket, 'error', error => {
|
|
196
|
+
cleanup()
|
|
197
|
+
reject(error)
|
|
198
|
+
})
|
|
199
|
+
cleanup = () => {
|
|
200
|
+
unsubscribeOpen()
|
|
201
|
+
unsubscribeError()
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
await this.openPromise
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function normalizeMessageData(data: unknown): Promise<string> {
|
|
209
|
+
if (typeof data === 'string') return data
|
|
210
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data)
|
|
211
|
+
if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data)
|
|
212
|
+
if (typeof Blob !== 'undefined' && data instanceof Blob) return await data.text()
|
|
213
|
+
return String(data)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getMessageEventData(event: unknown): unknown {
|
|
217
|
+
if (event && typeof event === 'object' && 'data' in event) {
|
|
218
|
+
return (event as { data: unknown }).data
|
|
219
|
+
}
|
|
220
|
+
return event
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function createWebSocket(endpoint: OpenWsEndpoint): unknown {
|
|
224
|
+
const WebSocketCtor = (globalThis as { WebSocket?: new (url: string) => unknown }).WebSocket
|
|
225
|
+
if (!WebSocketCtor) {
|
|
226
|
+
throw new Error('No global WebSocket constructor found. Pass a socket or custom Transport to the client.')
|
|
227
|
+
}
|
|
228
|
+
return new WebSocketCtor(endpointToUrl(endpoint))
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function endpointToUrl(endpoint: OpenWsEndpoint): string {
|
|
232
|
+
const scheme = endpoint.scheme || 'ws'
|
|
233
|
+
const host = endpoint.host || 'localhost'
|
|
234
|
+
const port = endpoint.port === undefined ? '' : `:${endpoint.port}`
|
|
235
|
+
const path = endpoint.path ? (endpoint.path.startsWith('/') ? endpoint.path : `/${endpoint.path}`) : ''
|
|
236
|
+
return `${scheme}://${host}${port}${path}`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getReadyState(socket: unknown): number | undefined {
|
|
240
|
+
const readyState = (socket as { readyState?: unknown }).readyState
|
|
241
|
+
return typeof readyState === 'number' ? readyState : undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function addSocketListener(
|
|
245
|
+
socket: unknown,
|
|
246
|
+
event: 'open' | 'message' | 'error',
|
|
247
|
+
handler: (...args: unknown[]) => void
|
|
248
|
+
): Unsubscribe {
|
|
249
|
+
const target = socket as {
|
|
250
|
+
on?: (event: string, handler: (...args: unknown[]) => void) => unknown
|
|
251
|
+
off?: (event: string, handler: (...args: unknown[]) => void) => unknown
|
|
252
|
+
removeListener?: (event: string, handler: (...args: unknown[]) => void) => unknown
|
|
253
|
+
addEventListener?: (event: string, handler: (event: unknown) => void) => unknown
|
|
254
|
+
removeEventListener?: (event: string, handler: (event: unknown) => void) => unknown
|
|
255
|
+
[key: string]: unknown
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (typeof target.on === 'function') {
|
|
259
|
+
target.on(event, handler)
|
|
260
|
+
return () => {
|
|
261
|
+
if (typeof target.off === 'function') target.off(event, handler)
|
|
262
|
+
else if (typeof target.removeListener === 'function') target.removeListener(event, handler)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (typeof target.addEventListener === 'function') {
|
|
267
|
+
const eventHandler = (event: unknown) => {
|
|
268
|
+
handler(event)
|
|
269
|
+
}
|
|
270
|
+
target.addEventListener(event, eventHandler)
|
|
271
|
+
return () => {
|
|
272
|
+
target.removeEventListener?.(event, eventHandler)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const propertyName = `on${event}`
|
|
277
|
+
const previous = target[propertyName]
|
|
278
|
+
const next = (...args: unknown[]) => {
|
|
279
|
+
if (typeof previous === 'function') {
|
|
280
|
+
previous(...args)
|
|
281
|
+
}
|
|
282
|
+
handler(...args)
|
|
283
|
+
}
|
|
284
|
+
target[propertyName] = next
|
|
285
|
+
return () => {
|
|
286
|
+
if (target[propertyName] === next) {
|
|
287
|
+
target[propertyName] = previous
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
<% } else { -%>
|
|
292
|
+
export function encodeEnvelope(envelope) {
|
|
293
|
+
return JSON.stringify(envelope)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function decodeEnvelope(data) {
|
|
297
|
+
return JSON.parse(data)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function canBindTransport(transport) {
|
|
301
|
+
return typeof transport.on === 'function'
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function bindTransport(transport, handler, options = {}) {
|
|
305
|
+
const handleData = async data => {
|
|
306
|
+
try {
|
|
307
|
+
await handler.handleRawMessage(await normalizeMessageData(data))
|
|
308
|
+
} catch (error) {
|
|
309
|
+
await handler.messageError?.(error)
|
|
310
|
+
if (options.closeOnError) {
|
|
311
|
+
transport.close?.()
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const handleSocketError = async error => {
|
|
316
|
+
await handler.socketError?.(error)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const nodeHandler = data => {
|
|
320
|
+
void handleData(data)
|
|
321
|
+
}
|
|
322
|
+
const nodeErrorHandler = error => {
|
|
323
|
+
void handleSocketError(error)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (typeof transport.on !== 'function') {
|
|
327
|
+
throw new Error('Transport must support on("message")')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const messageUnsubscribe = transport.on('message', nodeHandler)
|
|
331
|
+
const errorUnsubscribe = transport.on('error', nodeErrorHandler)
|
|
332
|
+
return () => {
|
|
333
|
+
if (typeof messageUnsubscribe === 'function') messageUnsubscribe()
|
|
334
|
+
if (typeof errorUnsubscribe === 'function') errorUnsubscribe()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export class WsTransport {
|
|
339
|
+
socket
|
|
340
|
+
socketUnsubscribe
|
|
341
|
+
openPromise
|
|
342
|
+
listeners = {
|
|
343
|
+
message: new Set(),
|
|
344
|
+
error: new Set(),
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
constructor(socket) {
|
|
348
|
+
if (socket) {
|
|
349
|
+
this.bindSocket(socket)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async connect(_roleName, endpoint) {
|
|
354
|
+
if (!this.socket) {
|
|
355
|
+
if (!endpoint) {
|
|
356
|
+
throw new Error('Cannot connect without a WebSocket endpoint')
|
|
357
|
+
}
|
|
358
|
+
this.bindSocket(createWebSocket(endpoint))
|
|
359
|
+
}
|
|
360
|
+
await this.waitForOpen()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async disconnect() {
|
|
364
|
+
this.close()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async send(data) {
|
|
368
|
+
await this.waitForOpen()
|
|
369
|
+
const socket = this.requireSocket()
|
|
370
|
+
if (typeof socket.send !== 'function') {
|
|
371
|
+
throw new Error('WebSocket object must support send(data)')
|
|
372
|
+
}
|
|
373
|
+
await socket.send(data)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
on(event, handler) {
|
|
377
|
+
this.listeners[event].add(handler)
|
|
378
|
+
return () => {
|
|
379
|
+
this.listeners[event].delete(handler)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
close() {
|
|
384
|
+
this.socket?.close?.()
|
|
385
|
+
this.socketUnsubscribe?.()
|
|
386
|
+
this.socket = undefined
|
|
387
|
+
this.socketUnsubscribe = undefined
|
|
388
|
+
this.openPromise = undefined
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
bindSocket(socket) {
|
|
392
|
+
this.socketUnsubscribe?.()
|
|
393
|
+
this.socket = socket
|
|
394
|
+
this.openPromise = undefined
|
|
395
|
+
|
|
396
|
+
const unsubscribeMessage = addSocketListener(socket, 'message', data => {
|
|
397
|
+
void this.emit('message', getMessageEventData(data))
|
|
398
|
+
})
|
|
399
|
+
const unsubscribeError = addSocketListener(socket, 'error', error => {
|
|
400
|
+
void this.emit('error', error)
|
|
401
|
+
})
|
|
402
|
+
this.socketUnsubscribe = () => {
|
|
403
|
+
unsubscribeMessage()
|
|
404
|
+
unsubscribeError()
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async emit(event, data) {
|
|
409
|
+
for (const handler of this.listeners[event]) {
|
|
410
|
+
await handler(data)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
requireSocket() {
|
|
415
|
+
if (!this.socket) {
|
|
416
|
+
throw new Error('WebSocket is not connected')
|
|
417
|
+
}
|
|
418
|
+
return this.socket
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async waitForOpen() {
|
|
422
|
+
const socket = this.requireSocket()
|
|
423
|
+
const readyState = getReadyState(socket)
|
|
424
|
+
if (readyState === undefined || readyState === 1) return
|
|
425
|
+
if (readyState === 2 || readyState === 3) {
|
|
426
|
+
throw new Error('WebSocket is closed')
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this.openPromise ??= new Promise((resolve, reject) => {
|
|
430
|
+
let cleanup = () => {}
|
|
431
|
+
const unsubscribeOpen = addSocketListener(socket, 'open', () => {
|
|
432
|
+
cleanup()
|
|
433
|
+
resolve()
|
|
434
|
+
})
|
|
435
|
+
const unsubscribeError = addSocketListener(socket, 'error', error => {
|
|
436
|
+
cleanup()
|
|
437
|
+
reject(error)
|
|
438
|
+
})
|
|
439
|
+
cleanup = () => {
|
|
440
|
+
unsubscribeOpen()
|
|
441
|
+
unsubscribeError()
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
await this.openPromise
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function normalizeMessageData(data) {
|
|
449
|
+
if (typeof data === 'string') return data
|
|
450
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data)
|
|
451
|
+
if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data)
|
|
452
|
+
if (typeof Blob !== 'undefined' && data instanceof Blob) return await data.text()
|
|
453
|
+
return String(data)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function getMessageEventData(event) {
|
|
457
|
+
if (event && typeof event === 'object' && 'data' in event) {
|
|
458
|
+
return event.data
|
|
459
|
+
}
|
|
460
|
+
return event
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function createWebSocket(endpoint) {
|
|
464
|
+
const WebSocketCtor = globalThis.WebSocket
|
|
465
|
+
if (!WebSocketCtor) {
|
|
466
|
+
throw new Error('No global WebSocket constructor found. Pass a socket or custom Transport to the client.')
|
|
467
|
+
}
|
|
468
|
+
return new WebSocketCtor(endpointToUrl(endpoint))
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function endpointToUrl(endpoint) {
|
|
472
|
+
const scheme = endpoint.scheme || 'ws'
|
|
473
|
+
const host = endpoint.host || 'localhost'
|
|
474
|
+
const port = endpoint.port === undefined ? '' : `:${endpoint.port}`
|
|
475
|
+
const path = endpoint.path ? (endpoint.path.startsWith('/') ? endpoint.path : `/${endpoint.path}`) : ''
|
|
476
|
+
return `${scheme}://${host}${port}${path}`
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function getReadyState(socket) {
|
|
480
|
+
return typeof socket.readyState === 'number' ? socket.readyState : undefined
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function addSocketListener(socket, event, handler) {
|
|
484
|
+
if (typeof socket.on === 'function') {
|
|
485
|
+
socket.on(event, handler)
|
|
486
|
+
return () => {
|
|
487
|
+
if (typeof socket.off === 'function') socket.off(event, handler)
|
|
488
|
+
else if (typeof socket.removeListener === 'function') socket.removeListener(event, handler)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (typeof socket.addEventListener === 'function') {
|
|
493
|
+
const eventHandler = event => {
|
|
494
|
+
handler(event)
|
|
495
|
+
}
|
|
496
|
+
socket.addEventListener(event, eventHandler)
|
|
497
|
+
return () => {
|
|
498
|
+
socket.removeEventListener?.(event, eventHandler)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const propertyName = `on${event}`
|
|
503
|
+
const previous = socket[propertyName]
|
|
504
|
+
const next = (...args) => {
|
|
505
|
+
if (typeof previous === 'function') {
|
|
506
|
+
previous(...args)
|
|
507
|
+
}
|
|
508
|
+
handler(...args)
|
|
509
|
+
}
|
|
510
|
+
socket[propertyName] = next
|
|
511
|
+
return () => {
|
|
512
|
+
if (socket[propertyName] === next) {
|
|
513
|
+
socket[propertyName] = previous
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
<% } -%>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<% const modelImportPath = ctx.isTypeScript ? `../models/${ctx.fileName}` : `../models/${ctx.fileName}/index.js` -%>
|
|
2
|
+
<% if (ctx.isTypeScript) { -%>
|
|
3
|
+
<% if (ctx.messages.length > 0) { -%>
|
|
4
|
+
import type {
|
|
5
|
+
<% for (const message of ctx.messages) { -%>
|
|
6
|
+
<%= message.payloadType %>,
|
|
7
|
+
<%= message.payloadType %>Init,
|
|
8
|
+
<% } -%>
|
|
9
|
+
} from '<%= modelImportPath %>'
|
|
10
|
+
<% } -%>
|
|
11
|
+
<% const importedPeerRoles = new Map(); for (const message of ctx.messages) { for (const fromRole of message.fromRoles ?? []) { if (fromRole.roleName !== ctx.roleName) importedPeerRoles.set(fromRole.roleName, fromRole) } } -%>
|
|
12
|
+
<% if (importedPeerRoles.size > 0) { -%>
|
|
13
|
+
import {
|
|
14
|
+
<% for (const fromRole of importedPeerRoles.values()) { -%>
|
|
15
|
+
<%= fromRole.roleClassName %>,
|
|
16
|
+
<% } -%>
|
|
17
|
+
} from './index'
|
|
18
|
+
<% } -%>
|
|
19
|
+
|
|
20
|
+
export interface <%= ctx.apiName %> {
|
|
21
|
+
<% for (const message of ctx.messages) { -%>
|
|
22
|
+
<%= message.methodName %>(payload: <%= message.payloadType %> | <%= message.payloadType %>Init): Promise<void>
|
|
23
|
+
<% } -%>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class <%= ctx.roleClassName %> {
|
|
27
|
+
static readonly CONFIG = {
|
|
28
|
+
name: <%- JSON.stringify(ctx.roleName) %>,
|
|
29
|
+
description: <%- JSON.stringify(ctx.description) %>,
|
|
30
|
+
messages: {
|
|
31
|
+
<% for (const message of ctx.messages) { -%>
|
|
32
|
+
<%= message.messageName %>: {
|
|
33
|
+
payload: <%- JSON.stringify(message.schema, null, 16) %>,
|
|
34
|
+
},
|
|
35
|
+
<% } -%>
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
static readonly endpoints = <%- JSON.stringify(ctx.endpoints, null, 4) %>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class <%= ctx.hostRoleClassName %> {
|
|
42
|
+
static get CONFIG() {
|
|
43
|
+
return {
|
|
44
|
+
name: <%- JSON.stringify(ctx.roleName) %>,
|
|
45
|
+
description: <%- JSON.stringify(ctx.description) %>,
|
|
46
|
+
handlers: {
|
|
47
|
+
<% for (const message of ctx.messages) { -%>
|
|
48
|
+
<%= message.messageName %>: {
|
|
49
|
+
payload: <%= ctx.roleClassName %>.CONFIG.messages.<%= message.messageName %>.payload,
|
|
50
|
+
<% if (message.fromRoles) { -%>
|
|
51
|
+
from: [<%= message.fromRoles.map(fromRole => fromRole.roleClassName).join(', ') %>],
|
|
52
|
+
<% } -%>
|
|
53
|
+
},
|
|
54
|
+
<% } -%>
|
|
55
|
+
},
|
|
56
|
+
endpoints: <%= ctx.roleClassName %>.endpoints,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
<% } else { -%>
|
|
61
|
+
<% const importedPeerRoles = new Map(); for (const message of ctx.messages) { for (const fromRole of message.fromRoles ?? []) { if (fromRole.roleName !== ctx.roleName) importedPeerRoles.set(fromRole.roleName, fromRole) } } -%>
|
|
62
|
+
<% if (importedPeerRoles.size > 0) { -%>
|
|
63
|
+
import {
|
|
64
|
+
<% for (const fromRole of importedPeerRoles.values()) { -%>
|
|
65
|
+
<%= fromRole.roleClassName %>,
|
|
66
|
+
<% } -%>
|
|
67
|
+
} from './index.js'
|
|
68
|
+
<% } -%>
|
|
69
|
+
|
|
70
|
+
export class <%= ctx.roleClassName %> {
|
|
71
|
+
static CONFIG = {
|
|
72
|
+
name: <%- JSON.stringify(ctx.roleName) %>,
|
|
73
|
+
description: <%- JSON.stringify(ctx.description) %>,
|
|
74
|
+
messages: {
|
|
75
|
+
<% for (const message of ctx.messages) { -%>
|
|
76
|
+
<%= message.messageName %>: {
|
|
77
|
+
payload: <%- JSON.stringify(message.schema, null, 16) %>,
|
|
78
|
+
},
|
|
79
|
+
<% } -%>
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
static endpoints = <%- JSON.stringify(ctx.endpoints, null, 4) %>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class <%= ctx.hostRoleClassName %> {
|
|
86
|
+
static get CONFIG() {
|
|
87
|
+
return {
|
|
88
|
+
name: <%- JSON.stringify(ctx.roleName) %>,
|
|
89
|
+
description: <%- JSON.stringify(ctx.description) %>,
|
|
90
|
+
handlers: {
|
|
91
|
+
<% for (const message of ctx.messages) { -%>
|
|
92
|
+
<%= message.messageName %>: {
|
|
93
|
+
payload: <%= ctx.roleClassName %>.CONFIG.messages.<%= message.messageName %>.payload,
|
|
94
|
+
<% if (message.fromRoles) { -%>
|
|
95
|
+
from: [<%= message.fromRoles.map(fromRole => fromRole.roleClassName).join(', ') %>],
|
|
96
|
+
<% } -%>
|
|
97
|
+
},
|
|
98
|
+
<% } -%>
|
|
99
|
+
},
|
|
100
|
+
endpoints: <%= ctx.roleClassName %>.endpoints,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
<% } -%>
|