Skip to content

Commit

Permalink
feat(io): narrow type of user on io session (#617)
Browse files Browse the repository at this point in the history
  • Loading branch information
pluvrt authored May 13, 2024
1 parent 442dcee commit 329dbcd
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 39 deletions.
5 changes: 5 additions & 0 deletions .changeset/lucky-humans-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pluv/io": minor
---

Narrowed types for `user` within the session object of an event resolver.
2 changes: 1 addition & 1 deletion packages/io/src/AbstractWebSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type EventType = keyof AbstractEventMap;
export interface AbstractWebSocketHandleErrorParams {
error: unknown;
message?: string;
session?: WebSocketSession;
session?: WebSocketSession<any>;
}

export type AbstractListener<TType extends keyof AbstractEventMap> = (
Expand Down
79 changes: 58 additions & 21 deletions packages/io/src/IORoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export type IORoomConfig<
context: TContext & InferPlatformRoomContextType<TPlatform>;
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
debug: boolean;
events: InferEventConfig<TPlatform, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
events: InferEventConfig<TPlatform, TAuthorize, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
platform: TPlatform;
};

Expand Down Expand Up @@ -98,11 +98,19 @@ export class IORoom<

private _doc: AbstractCrdtDoc<any>;
private _listeners: IORoomListeners<TPlatform>;
private _sessions = new Map<string, WebSocketSession>();
private _sessions = new Map<string, WebSocketSession<TAuthorize>>();
private _uninitialize: (() => Promise<void>) | null = null;

readonly _authorize: TAuthorize | null = null;
readonly _events: InferEventConfig<TPlatform, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
readonly _events: InferEventConfig<
TPlatform,
TAuthorize,
TContext,
TInput,
TOutputBroadcast,
TOutputSelf,
TOutputSync
>;

readonly id: string;

Expand Down Expand Up @@ -192,7 +200,7 @@ export class IORoom<
}

const currentTime = new Date().getTime();
const session: WebSocketSession = {
const session: WebSocketSession<TAuthorize> = {
id: sessionId,
presence: null,
quit: false,
Expand Down Expand Up @@ -248,7 +256,7 @@ export class IORoom<
});
}

private _emitQuitters(quitters: readonly WebSocketSession[]): void {
private _emitQuitters(quitters: readonly WebSocketSession<TAuthorize>[]): void {
quitters.forEach((quitter) => {
this._broadcast({
message: {
Expand All @@ -260,7 +268,7 @@ export class IORoom<
});
}

private _emitRegistered(session: WebSocketSession): void {
private _emitRegistered(session: WebSocketSession<TAuthorize>): void {
this._sendSelfMessage(
{
type: "$REGISTERED",
Expand All @@ -285,11 +293,11 @@ export class IORoom<
private async _getAuthorizedUser(
token: Maybe<string>,
context: InferPlatformRoomContextType<TPlatform>,
): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>> | null> {
): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>>> {
const ioAuthorize = this._getIOAuthorize(context);

if (!ioAuthorize) return null;
if (!token) return null;
if (!ioAuthorize) return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
if (!token) return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;

const payload = await authorize({
platform: this._platform,
Expand All @@ -300,29 +308,39 @@ export class IORoom<
this._logDebug(colors.blue("Could not decode token:"));
this._logDebug(token);

return null;
return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
}

if (payload.room !== this.id) {
this._logDebug(colors.blue(`Token is not authorized for room ${this.id}:`));
this._logDebug(colors.blue("Received:"), payload.room);
this._logDebug(token);

return null;
return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
}

try {
return ioAuthorize.user.parse(payload.user) ?? null;
} catch {
this._logDebug(`${colors.blue("Token fails validation:")} ${token}`);

return null;
return null as InferIOAuthorizeUser<InferIOAuthorize<this>>;
}
}

private _getEventConfig(
message: EventMessage<string, any>,
): InferEventConfig<TPlatform, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>[keyof TInput] | null {
):
| InferEventConfig<
TPlatform,
TAuthorize,
TContext,
TInput,
TOutputBroadcast,
TOutputSelf,
TOutputSync
>[keyof TInput]
| null {
return this._events[message.type as keyof TInput] ?? null;
}

Expand All @@ -344,19 +362,36 @@ export class IORoom<

private _getEventResolverObject(
message: EventMessage<string, any>,
): EventResolverObject<TPlatform, TContext, TInput[string], TOutputBroadcast, TOutputSelf, TOutputSync> {
): EventResolverObject<
TPlatform,
TAuthorize,
TContext,
TInput[string],
TOutputBroadcast,
TOutputSelf,
TOutputSync
> {
const eventConfig = this._getEventConfig(message);

if (!eventConfig) return {};

const resolver:
| EventResolver<
TPlatform,
TAuthorize,
TContext & InferPlatformRoomContextType<TPlatform> & InferPlatformEventContextType<TPlatform>,
TInput[string],
TOutputBroadcast
>
| EventResolverObject<TPlatform, TContext, TInput[string], TOutputBroadcast, TOutputSelf, TOutputSync> =
eventConfig.resolver;
| EventResolverObject<
TPlatform,
TAuthorize,
TContext,
TInput[string],
TOutputBroadcast,
TOutputSelf,
TOutputSync
> = eventConfig.resolver;

return typeof resolver === "function" ? { broadcast: resolver } : { ...resolver };
}
Expand Down Expand Up @@ -401,7 +436,7 @@ export class IORoom<
this._debug && console.log(...data);
}

private _onClose(session: WebSocketSession, callback?: () => void): () => void {
private _onClose(session: WebSocketSession<TAuthorize>, callback?: () => void): () => void {
return (): void => {
if (!this._uninitialize) return;

Expand Down Expand Up @@ -434,11 +469,11 @@ export class IORoom<
}

private _onMessage(
session: WebSocketSession,
session: WebSocketSession<TAuthorize>,
platformEventContext: InferPlatformEventContextType<TPlatform>,
): (event: AbstractMessageEvent) => void {
return (event: AbstractMessageEvent): void => {
const baseContext: EventResolverContext<TContext> = {
const baseContext: EventResolverContext<TPlatform, TAuthorize, TContext> = {
context: this._context,
doc: this._doc,
room: this.id,
Expand Down Expand Up @@ -475,6 +510,8 @@ export class IORoom<
}

const extendedContext: EventResolverContext<
TPlatform,
TAuthorize,
TContext & InferPlatformRoomContextType<TPlatform> & InferPlatformEventContextType<TPlatform>
> = {
...baseContext,
Expand Down Expand Up @@ -569,7 +606,7 @@ export class IORoom<
const _session = this._sessions.get(id);

return _session ? dict.set(id, _session) : dict;
}, new Map<string, WebSocketSession>()) ?? this._sessions;
}, new Map<string, WebSocketSession<TAuthorize>>()) ?? this._sessions;

Array.from(sessions.values()).forEach((_session) => {
_session.webSocket.sendMessage({
Expand Down Expand Up @@ -603,7 +640,7 @@ export class IORoom<

if (!senderId) return;

const context: EventResolverContext<TContext> = {
const context: EventResolverContext<TPlatform, TAuthorize, TContext> = {
context: this._context,
doc: this._doc,
room: this.id,
Expand Down
21 changes: 15 additions & 6 deletions packages/io/src/PluvIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
Spread,
} from "@pluv/types";
import colors from "kleur";
import type { AbstractPlatform, InferPlatformEventContextType, InferPlatformRoomContextType } from "./AbstractPlatform";
import type { AbstractPlatform, InferPlatformRoomContextType } from "./AbstractPlatform";
import type { IORoomListenerEvent } from "./IORoom";
import { IORoom } from "./IORoom";
import type { JWTEncodeParams } from "./authorize";
Expand Down Expand Up @@ -59,7 +59,7 @@ export interface PluvIOListeners<TPlatform extends AbstractPlatform> {

export type PluvIOConfig<
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> | null = null,
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
TContext extends JsonObject = {},
TInput extends EventRecord<string, any> = {},
TOutputBroadcast extends EventRecord<string, any> = {},
Expand All @@ -70,7 +70,7 @@ export type PluvIOConfig<
context?: TContext;
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
debug?: boolean;
events?: InferEventConfig<TPlatform, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
events?: InferEventConfig<TPlatform, TAuthorize, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
getInitialStorage?: GetInitialStorageFn<TPlatform>;
platform: TPlatform;
};
Expand Down Expand Up @@ -98,7 +98,15 @@ export class PluvIO<
readonly _context: TContext = {} as TContext;
readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
readonly _debug: boolean;
readonly _events: InferEventConfig<TPlatform, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync> = {
readonly _events: InferEventConfig<
TPlatform,
TAuthorize,
TContext,
TInput,
TOutputBroadcast,
TOutputSelf,
TOutputSync
> = {
$GET_OTHERS: {
resolver: {
sync: (data, { room, session, sessions }) => {
Expand Down Expand Up @@ -215,7 +223,7 @@ export class PluvIO<
return { $STORAGE_UPDATED: { state: encodedState } };
},
},
} as InferEventConfig<TPlatform, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
} as InferEventConfig<TPlatform, TAuthorize, TContext, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>;
readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
readonly _listeners: PluvIOListeners<TPlatform>;
readonly _platform: TPlatform;
Expand Down Expand Up @@ -277,7 +285,7 @@ export class PluvIO<
TResultSync extends EventRecord<string, any> = {},
>(
event: TEvent,
config: EventConfig<TPlatform, TContext, TData, TResultBroadcast, TResultSelf, TResultSync>,
config: EventConfig<TPlatform, TAuthorize, TContext, TData, TResultBroadcast, TResultSelf, TResultSync>,
): PluvIO<
TPlatform,
TAuthorize,
Expand All @@ -295,6 +303,7 @@ export class PluvIO<
[event]: config,
} as InferEventConfig<
TPlatform,
TAuthorize,
TContext,
EventRecord<TEvent, TData>,
TResultBroadcast extends EventRecord<string, any> ? TResultBroadcast : {},
Expand Down
Loading

0 comments on commit 329dbcd

Please sign in to comment.