@elysiajs/websocket
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -1,3 +0,17 @@ | ||
import type { WebSocketHandler } from 'bun'; | ||
import { Elysia } from 'elysia'; | ||
/// <reference types="bun-types" /> | ||
import type { ServerWebSocket, WebSocketHandler } from 'bun'; | ||
import { Elysia, type TypedSchema, UnwrapSchema } from 'elysia'; | ||
import type { ElysiaWSContext } from './types'; | ||
export declare class ElysiaWS<WS extends ElysiaWSContext<any> = ElysiaWSContext, Schema extends TypedSchema = TypedSchema> { | ||
raw: WS; | ||
data: WS['data']; | ||
isSubscribed: WS['isSubscribed']; | ||
constructor(ws: WS); | ||
publish(topic: string, data: UnwrapSchema<Schema['response']>, compress?: boolean): this; | ||
send(data: UnwrapSchema<Schema['response']>): this; | ||
subscribe(room: string): this; | ||
unsubscribe(room: string): this; | ||
cork(callback: (ws: ServerWebSocket<any>) => any): this; | ||
close(): this; | ||
} | ||
export declare const websocket: (config?: Omit<WebSocketHandler, 'open' | 'message' | 'close' | 'drain'>) => (app: Elysia) => Elysia<import("elysia").ElysiaInstance<{ | ||
@@ -4,0 +18,0 @@ store: Record<typeof import("elysia").SCHEMA, {}>; |
import { Elysia, getPath, Router, createValidationError, getSchemaValidator } from 'elysia'; | ||
import { nanoid } from 'nanoid'; | ||
export class ElysiaWS { | ||
constructor(ws) { | ||
this.raw = ws; | ||
this.data = ws.data; | ||
this.isSubscribed = ws.isSubscribed; | ||
} | ||
publish(topic, data, compress) { | ||
if (typeof data === 'object') | ||
data = JSON.stringify(data); | ||
this.raw.publish(topic, data, compress); | ||
return this; | ||
} | ||
send(data) { | ||
if (typeof data === 'object') | ||
data = JSON.stringify(data); | ||
this.raw.send(data); | ||
return this; | ||
} | ||
subscribe(room) { | ||
this.raw.subscribe(room); | ||
return this; | ||
} | ||
unsubscribe(room) { | ||
this.raw.unsubscribe(room); | ||
return this; | ||
} | ||
cork(callback) { | ||
this.raw.cork(callback); | ||
return this; | ||
} | ||
close() { | ||
this.raw.close(); | ||
return this; | ||
} | ||
} | ||
export const websocket = (config) => (app) => { | ||
app.websocketRouter = new Router(); | ||
if (!app.websocketRouter) | ||
app.websocketRouter = new Router(); | ||
if (!app.config.serve) | ||
@@ -12,5 +48,7 @@ app.config.serve = { | ||
return; | ||
const route = app.websocketRouter.find(getPath((ws?.data).request.url), (ws?.data).request.url.indexOf('?'))?.store['ws']; | ||
const url = (ws?.data).request.url; | ||
const index = url.indexOf('?'); | ||
const route = app.websocketRouter.find(getPath(url, index), index)?.store['subscribe']; | ||
if (route && route.open) | ||
route.open(ws); | ||
route.open(new ElysiaWS(ws)); | ||
}, | ||
@@ -20,5 +58,10 @@ message(ws, message) { | ||
return; | ||
const route = app.websocketRouter.find(getPath((ws?.data).request.url), (ws?.data).request.url.indexOf('?'))?.store['ws']; | ||
if (route && route.message) { | ||
message = message.toString(); | ||
const url = (ws?.data).request.url; | ||
const index = url.indexOf('?'); | ||
const route = app.websocketRouter.find(getPath(url, index), index)?.store['subscribe']; | ||
if (!route?.message) | ||
return; | ||
message = message.toString(); | ||
const start = message.charCodeAt(0); | ||
if (start === 47 || start === 123) | ||
try { | ||
@@ -28,9 +71,6 @@ message = JSON.parse(message); | ||
catch (error) { } | ||
if (ws.data.message && | ||
ws.data.message?.Check(message)) | ||
return void ws.send(createValidationError('message', ws.data | ||
.message, message).cause); | ||
route.message(ws, message); | ||
} | ||
return undefined; | ||
if (ws.data.message?.Check(message) === false) | ||
return void route.message(createValidationError('message', ws.data | ||
.message, message).cause); | ||
route.message(new ElysiaWS(ws), message); | ||
}, | ||
@@ -41,5 +81,7 @@ close(ws, code, reason) { | ||
const queryIndex = (ws?.data).request.url.indexOf('?'); | ||
const route = app.websocketRouter.find(getPath((ws?.data).request.url), queryIndex)?.store['ws']; | ||
const url = (ws?.data).request.url; | ||
const index = url.indexOf('?'); | ||
const route = app.websocketRouter.find(getPath(url, index), index)?.store['subscribe']; | ||
if (route && route.close) | ||
route.close(ws, code, reason); | ||
route.close(new ElysiaWS(ws), code, reason); | ||
}, | ||
@@ -49,5 +91,7 @@ drain(ws) { | ||
return; | ||
const route = app.websocketRouter.find(getPath((ws?.data).request.url), (ws?.data).request.url.indexOf('?'))?.store['ws']; | ||
const url = (ws?.data).request.url; | ||
const index = url.indexOf('?'); | ||
const route = app.websocketRouter.find(getPath(url, index), index)?.store['subscribe']; | ||
if (route && route.drain) | ||
route.drain(ws); | ||
route.drain(new ElysiaWS(ws)); | ||
} | ||
@@ -60,4 +104,4 @@ } | ||
if (!this.websocketRouter) | ||
throw new Error("Can't find WebSocket. Please register WebSocket plugin first"); | ||
this.websocketRouter.register(path)['ws'] = options; | ||
this.websocketRouter = new Router(); | ||
this.websocketRouter.register(path)['subscribe'] = options; | ||
return this.get(path, (context) => { | ||
@@ -64,0 +108,0 @@ if (this.server.upgrade(context.request, { |
@@ -7,2 +7,3 @@ /// <reference types="bun-types" /> | ||
import type { TypeCheck } from '@sinclair/typebox/compiler'; | ||
import { ElysiaWS } from '.'; | ||
export declare type WebSocketSchemaToRoute<Schema extends TypedSchema> = { | ||
@@ -15,3 +16,3 @@ body: UnwrapSchema<Schema['body']> extends Record<string, any> ? UnwrapSchema<Schema['body']> : undefined; | ||
}; | ||
export declare type ElysiaWebSocket<Schema extends TypedSchema = TypedSchema, Path extends string = string> = ServerWebSocket<Context<ExtractPath<Path> extends never ? WebSocketSchemaToRoute<Schema> : Omit<WebSocketSchemaToRoute<Schema>, 'params'> & { | ||
export declare type ElysiaWSContext<Schema extends TypedSchema = TypedSchema, Path extends string = string> = ServerWebSocket<Context<ExtractPath<Path> extends never ? WebSocketSchemaToRoute<Schema> : Omit<WebSocketSchemaToRoute<Schema>, 'params'> & { | ||
params: Record<ExtractPath<Path>, string>; | ||
@@ -37,8 +38,8 @@ }> & { | ||
headers?: HeadersInit | WebSocketHeaderHandler<Schema>; | ||
open?: (ws: ElysiaWebSocket<Schema, Path>) => void | Promise<void>; | ||
message?: (ws: ElysiaWebSocket<Schema, Path>, message: Schema['body'] extends NonNullable<Schema['body']> ? Static<NonNullable<Schema['body']>> : string) => any; | ||
close?: (ws: ElysiaWebSocket<Schema, Path>) => any; | ||
drain?: (ws: ElysiaWebSocket<Schema, Path>, code: number, reason: string) => any; | ||
open?: (ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema>) => void | Promise<void>; | ||
message?: (ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema>, message: Schema['body'] extends NonNullable<Schema['body']> ? Static<NonNullable<Schema['body']>> : string) => any; | ||
close?: (ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema>) => any; | ||
drain?: (ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema>, code: number, reason: string) => any; | ||
}): Instance extends Elysia<infer Instance> ? ElysiaRoute<'subscribe', Schema, Instance, Path, Schema['response']> : this; | ||
} | ||
} |
{ | ||
"name": "@elysiajs/websocket", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "Plugin for Elysia that add support for websocket", | ||
@@ -5,0 +5,0 @@ "author": { |
179
src/index.ts
@@ -1,2 +0,2 @@ | ||
import type { WebSocketHandler } from 'bun' | ||
import type { ServerWebSocket, WebSocketHandler } from 'bun' | ||
@@ -9,3 +9,5 @@ import { | ||
getSchemaValidator, | ||
type Context | ||
type Context, | ||
type TypedSchema, | ||
UnwrapSchema | ||
} from 'elysia' | ||
@@ -15,4 +17,65 @@ import { nanoid } from 'nanoid' | ||
import type { TSchema } from '@sinclair/typebox' | ||
import type { ElysiaWebSocket } from './types' | ||
import type { ElysiaWSContext } from './types' | ||
export class ElysiaWS< | ||
WS extends ElysiaWSContext<any> = ElysiaWSContext, | ||
Schema extends TypedSchema = TypedSchema | ||
> { | ||
raw: WS | ||
data: WS['data'] | ||
isSubscribed: WS['isSubscribed'] | ||
constructor(ws: WS) { | ||
this.raw = ws | ||
this.data = ws.data | ||
this.isSubscribed = ws.isSubscribed | ||
} | ||
publish( | ||
topic: string, | ||
data: UnwrapSchema<Schema['response']>, | ||
compress?: boolean | ||
) { | ||
// @ts-ignore | ||
if (typeof data === 'object') data = JSON.stringify(data) | ||
this.raw.publish(topic, data as string, compress) | ||
return this | ||
} | ||
send(data: UnwrapSchema<Schema['response']>) { | ||
// @ts-ignore | ||
if (typeof data === 'object') data = JSON.stringify(data) | ||
this.raw.send(data as string) | ||
return this | ||
} | ||
subscribe(room: string) { | ||
this.raw.subscribe(room) | ||
return this | ||
} | ||
unsubscribe(room: string) { | ||
this.raw.unsubscribe(room) | ||
return this | ||
} | ||
cork(callback: (ws: ServerWebSocket<any>) => any) { | ||
this.raw.cork(callback) | ||
return this | ||
} | ||
close() { | ||
this.raw.close() | ||
return this | ||
} | ||
} | ||
/** | ||
@@ -38,3 +101,3 @@ * Register websocket config for Elysia | ||
(app: Elysia) => { | ||
app.websocketRouter = new Router() | ||
if (!app.websocketRouter) app.websocketRouter = new Router() | ||
@@ -48,28 +111,30 @@ if (!app.config.serve) | ||
const url = (ws?.data as unknown as Context).request.url | ||
const index = url.indexOf('?') | ||
const route = app.websocketRouter.find( | ||
getPath( | ||
(ws?.data as unknown as Context).request.url | ||
), | ||
( | ||
ws?.data as unknown as Context | ||
).request.url.indexOf('?') | ||
)?.store['ws'] | ||
getPath(url, index), | ||
index | ||
)?.store['subscribe'] | ||
if (route && route.open) route.open(ws) | ||
if (route && route.open) | ||
route.open(new ElysiaWS(ws as any)) | ||
}, | ||
message(ws, message) { | ||
message(ws, message): void { | ||
if (!ws.data) return | ||
const url = (ws?.data as unknown as Context).request.url | ||
const index = url.indexOf('?') | ||
const route = app.websocketRouter.find( | ||
getPath( | ||
(ws?.data as unknown as Context).request.url | ||
), | ||
( | ||
ws?.data as unknown as Context | ||
).request.url.indexOf('?') | ||
)?.store['ws'] | ||
getPath(url, index), | ||
index | ||
)?.store['subscribe'] | ||
if (route && route.message) { | ||
message = message.toString() | ||
if (!route?.message) return | ||
message = message.toString() | ||
const start = message.charCodeAt(0) | ||
if (start === 47 || start === 123) | ||
try { | ||
@@ -79,22 +144,17 @@ message = JSON.parse(message) | ||
if ( | ||
(ws.data as ElysiaWebSocket['data']).message && | ||
( | ||
ws.data as ElysiaWebSocket['data'] | ||
).message?.Check(message) | ||
if ( | ||
(ws.data as ElysiaWSContext['data']).message?.Check( | ||
message | ||
) === false | ||
) | ||
return void route.message( | ||
createValidationError( | ||
'message', | ||
(ws.data as ElysiaWSContext['data']) | ||
.message, | ||
message | ||
).cause as string | ||
) | ||
return void ws.send( | ||
createValidationError( | ||
'message', | ||
(ws.data as ElysiaWebSocket['data']) | ||
.message, | ||
message | ||
).cause as string | ||
) | ||
route.message(ws, message) | ||
} | ||
// ? noImplicitReturns | ||
return undefined | ||
route.message(new ElysiaWS(ws as any), message) | ||
}, | ||
@@ -108,10 +168,12 @@ close(ws, code, reason) { | ||
const url = (ws?.data as unknown as Context).request.url | ||
const index = url.indexOf('?') | ||
const route = app.websocketRouter.find( | ||
getPath( | ||
(ws?.data as unknown as Context).request.url | ||
), | ||
queryIndex | ||
)?.store['ws'] | ||
getPath(url, index), | ||
index | ||
)?.store['subscribe'] | ||
if (route && route.close) route.close(ws, code, reason) | ||
if (route && route.close) | ||
route.close(new ElysiaWS(ws as any), code, reason) | ||
}, | ||
@@ -121,12 +183,12 @@ drain(ws) { | ||
const url = (ws?.data as unknown as Context).request.url | ||
const index = url.indexOf('?') | ||
const route = app.websocketRouter.find( | ||
getPath( | ||
(ws?.data as unknown as Context).request.url | ||
), | ||
( | ||
ws?.data as unknown as Context | ||
).request.url.indexOf('?') | ||
)?.store['ws'] | ||
getPath(url, index), | ||
index | ||
)?.store['subscribe'] | ||
if (route && route.drain) route.drain(ws) | ||
if (route && route.drain) | ||
route.drain(new ElysiaWS(ws as any)) | ||
} | ||
@@ -141,8 +203,5 @@ } | ||
Elysia.prototype.ws = function (path, options) { | ||
if (!this.websocketRouter) | ||
throw new Error( | ||
"Can't find WebSocket. Please register WebSocket plugin first" | ||
) | ||
if (!this.websocketRouter) this.websocketRouter = new Router() | ||
this.websocketRouter.register(path)['ws'] = options | ||
this.websocketRouter.register(path)['subscribe'] = options | ||
@@ -162,3 +221,3 @@ return this.get( | ||
message: getSchemaValidator(options.schema?.body) | ||
} as ElysiaWebSocket['data'] | ||
} as ElysiaWSContext['data'] | ||
}) | ||
@@ -165,0 +224,0 @@ ) |
@@ -21,2 +21,3 @@ import type { ServerWebSocket, WebSocketHandler } from 'bun' | ||
import type { TypeCheck } from '@sinclair/typebox/compiler' | ||
import { ElysiaWS } from '.' | ||
@@ -41,3 +42,3 @@ export type WebSocketSchemaToRoute<Schema extends TypedSchema> = { | ||
export type ElysiaWebSocket< | ||
export type ElysiaWSContext< | ||
Schema extends TypedSchema = TypedSchema, | ||
@@ -112,3 +113,3 @@ Path extends string = string | ||
open?: ( | ||
ws: ElysiaWebSocket<Schema, Path> | ||
ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema> | ||
) => void | Promise<void> | ||
@@ -125,3 +126,3 @@ | ||
message?: ( | ||
ws: ElysiaWebSocket<Schema, Path>, | ||
ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema>, | ||
message: Schema['body'] extends NonNullable<Schema['body']> | ||
@@ -138,3 +139,5 @@ ? Static<NonNullable<Schema['body']>> | ||
*/ | ||
close?: (ws: ElysiaWebSocket<Schema, Path>) => any | ||
close?: ( | ||
ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema> | ||
) => any | ||
@@ -147,3 +150,3 @@ /** | ||
drain?: ( | ||
ws: ElysiaWebSocket<Schema, Path>, | ||
ws: ElysiaWS<ElysiaWSContext<Schema, Path>, Schema>, | ||
code: number, | ||
@@ -154,5 +157,11 @@ reason: string | ||
): Instance extends Elysia<infer Instance> | ||
? ElysiaRoute<'subscribe', Schema, Instance, Path, Schema['response']> | ||
? ElysiaRoute< | ||
'subscribe', | ||
Schema, | ||
Instance, | ||
Path, | ||
Schema['response'] | ||
> | ||
: this | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24056
526