Skip to content

Commit 16dff89

Browse files
committed
feat: support extra meta field, and passing instance for group mode to differentiate instances
1 parent f5bb5a4 commit 16dff89

File tree

3 files changed

+63
-26
lines changed

3 files changed

+63
-26
lines changed

src/index.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export type PromisifyFn<T> = ReturnType<T> extends Promise<any>
55
: (...args: ArgumentsType<T>) => Promise<Awaited<ReturnType<T>>>
66
export type Thenable<T> = T | PromiseLike<T>
77

8-
export type BirpcResolver = (name: string, resolved: (...args: unknown[]) => unknown) => Thenable<((...args: unknown[]) => unknown) | undefined>
8+
export type BirpcResolver<This> = (this: This, name: string, resolved: (...args: unknown[]) => unknown) => Thenable<((...args: any[]) => any) | undefined>
99

1010
export interface ChannelOptions {
1111
/**
@@ -37,13 +37,18 @@ export interface ChannelOptions {
3737
* Call the methods with the RPC context or the original functions object
3838
*/
3939
bind?: 'rpc' | 'functions'
40+
41+
/**
42+
* Custom meta data to attached to the RPC instance's `$meta` property
43+
*/
44+
meta?: any
4045
}
4146

42-
export interface EventOptions<Remote> {
47+
export interface EventOptions<RemoteFunctions, LocalFunctions extends object = Record<string, never>> {
4348
/**
4449
* Names of remote functions that do not need response.
4550
*/
46-
eventNames?: (keyof Remote)[]
51+
eventNames?: (keyof RemoteFunctions)[]
4752

4853
/**
4954
* Maximum timeout for waiting for response, in milliseconds.
@@ -57,7 +62,7 @@ export interface EventOptions<Remote> {
5762
*
5863
* For advanced use cases only
5964
*/
60-
resolver?: BirpcResolver
65+
resolver?: BirpcResolver<BirpcReturn<RemoteFunctions, LocalFunctions>>
6166

6267
/**
6368
* Hook triggered before an event is sent to the remote
@@ -66,38 +71,38 @@ export interface EventOptions<Remote> {
6671
* @param next - Function to continue the request
6772
* @param resolve - Function to resolve the response directly
6873
*/
69-
onRequest?: (req: Request, next: (req?: Request) => Promise<any>, resolve: (res: any) => void) => void | Promise<void>
74+
onRequest?: (this: BirpcReturn<RemoteFunctions, LocalFunctions>, req: Request, next: (req?: Request) => Promise<any>, resolve: (res: any) => void) => void | Promise<void>
7075

7176
/**
7277
* Custom error handler
7378
*
7479
* @deprecated use `onFunctionError` and `onGeneralError` instead
7580
*/
76-
onError?: (error: Error, functionName: string, args: any[]) => boolean | void
81+
onError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions>, error: Error, functionName: string, args: any[]) => boolean | void
7782

7883
/**
7984
* Custom error handler for errors occurred in local functions being called
8085
*
8186
* @returns `true` to prevent the error from being thrown
8287
*/
83-
onFunctionError?: (error: Error, functionName: string, args: any[]) => boolean | void
88+
onFunctionError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions>, error: Error, functionName: string, args: any[]) => boolean | void
8489

8590
/**
8691
* Custom error handler for errors occurred during serialization or messsaging
8792
*
8893
* @returns `true` to prevent the error from being thrown
8994
*/
90-
onGeneralError?: (error: Error, functionName?: string, args?: any[]) => boolean | void
95+
onGeneralError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions>, error: Error, functionName?: string, args?: any[]) => boolean | void
9196

9297
/**
9398
* Custom error handler for timeouts
9499
*
95100
* @returns `true` to prevent the error from being thrown
96101
*/
97-
onTimeoutError?: (functionName: string, args: any[]) => boolean | void
102+
onTimeoutError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions>, functionName: string, args: any[]) => boolean | void
98103
}
99104

100-
export type BirpcOptions<Remote> = EventOptions<Remote> & ChannelOptions
105+
export type BirpcOptions<RemoteFunctions, LocalFunctions extends object = Record<string, never>> = EventOptions<RemoteFunctions, LocalFunctions> & ChannelOptions
101106

102107
export type BirpcFn<T> = PromisifyFn<T> & {
103108
/**
@@ -129,6 +134,10 @@ export interface BirpcReturnBuiltin<
129134
* Whether the RPC is closed
130135
*/
131136
readonly $closed: boolean
137+
/**
138+
* Custom meta data attached to the RPC instance
139+
*/
140+
readonly $meta: any
132141
/**
133142
* Close the RPC connection
134143
*/
@@ -259,7 +268,7 @@ const random = Math.random.bind(Math)
259268

260269
export function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(
261270
$functions: LocalFunctions,
262-
options: BirpcOptions<RemoteFunctions>,
271+
options: BirpcOptions<RemoteFunctions, LocalFunctions>,
263272
): BirpcReturn<RemoteFunctions, LocalFunctions> {
264273
const {
265274
post,
@@ -277,6 +286,7 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
277286

278287
const _rpcPromiseMap = new Map<string, PromiseEntry>()
279288
let _promiseInit: Promise<any> | any
289+
let rpc: BirpcReturn<RemoteFunctions, LocalFunctions>
280290

281291
async function _call(
282292
method: string,
@@ -320,7 +330,7 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
320330
timeoutId = setTimeout(() => {
321331
try {
322332
// Custom onTimeoutError handler can throw its own error too
323-
const handleResult = options.onTimeoutError?.(method, args)
333+
const handleResult = options.onTimeoutError?.call(rpc, method, args)
324334
if (handleResult !== true)
325335
throw new Error(`[birpc] timeout on calling "${method}"`)
326336
}
@@ -342,12 +352,12 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
342352

343353
try {
344354
if (options.onRequest)
345-
await options.onRequest(req, handler, resolve)
355+
await options.onRequest.call(rpc, req, handler, resolve)
346356
else
347357
await handler()
348358
}
349359
catch (e) {
350-
if (options.onGeneralError?.(e as Error) !== true)
360+
if (options.onGeneralError?.call(rpc, e as Error) !== true)
351361
throw e
352362
return
353363
}
@@ -377,11 +387,14 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
377387
get $closed() {
378388
return $closed
379389
},
390+
get $meta() {
391+
return options.meta
392+
},
380393
$close,
381394
$functions,
382395
}
383396

384-
const rpc = new Proxy({}, {
397+
rpc = new Proxy({}, {
385398
get(_, method: string) {
386399
if (Object.prototype.hasOwnProperty.call(builtinMethods, method))
387400
return (builtinMethods as any)[method]
@@ -440,7 +453,7 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
440453
msg = deserialize(data) as RPCMessage
441454
}
442455
catch (e) {
443-
if (options.onGeneralError?.(e as Error) !== true)
456+
if (options.onGeneralError?.call(rpc, e as Error) !== true)
444457
throw e
445458
return
446459
}
@@ -449,7 +462,7 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
449462
const { m: method, a: args, o: optional } = msg
450463
let result, error: any
451464
let fn = await (resolver
452-
? resolver(method, ($functions as any)[method])
465+
? resolver.call(rpc, method, ($functions as any)[method])
453466
: ($functions as any)[method])
454467

455468
if (optional)
@@ -470,9 +483,9 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
470483
if (msg.i) {
471484
// Error handling
472485
if (error && options.onError)
473-
options.onError(error, method, args)
486+
options.onError.call(rpc, error, method, args)
474487
if (error && options.onFunctionError) {
475-
if (options.onFunctionError(error, method, args) === true)
488+
if (options.onFunctionError.call(rpc, error, method, args) === true)
476489
return
477490
}
478491

@@ -484,7 +497,7 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
484497
}
485498
catch (e) {
486499
error = e
487-
if (options.onGeneralError?.(e as Error, method, args) !== true)
500+
if (options.onGeneralError?.call(rpc, e as Error, method, args) !== true)
488501
throw e
489502
}
490503
}
@@ -493,7 +506,7 @@ export function createBirpc<RemoteFunctions = Record<string, never>, LocalFuncti
493506
await post(serialize(<Response>{ t: TYPE_RESPONSE, i: msg.i, e: error }), ...extra)
494507
}
495508
catch (e) {
496-
if (options.onGeneralError?.(e as Error, method, args) !== true)
509+
if (options.onGeneralError?.call(rpc, e as Error, method, args) !== true)
497510
throw e
498511
}
499512
}
@@ -533,7 +546,7 @@ export function cachedMap<T, R>(items: T[], fn: ((i: T) => R)): R[] {
533546
export function createBirpcGroup<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(
534547
functions: LocalFunctions,
535548
channels: ChannelOptions[] | (() => ChannelOptions[]),
536-
options: EventOptions<RemoteFunctions> = {},
549+
options: EventOptions<RemoteFunctions, LocalFunctions> = {},
537550
): BirpcGroup<RemoteFunctions, LocalFunctions> {
538551
const getChannels = () => typeof channels === 'function' ? channels() : channels
539552
const getClients = (channels = getChannels()) => cachedMap(channels, s => createBirpc(functions, { ...options, ...s }))

test/group.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,29 @@ it('group', async () => {
2020
await new Promise(resolve => setTimeout(resolve, 100))
2121
channel1.port1.on('message', fn)
2222
},
23+
meta: {
24+
name: 'client1',
25+
},
2326
},
2427
)
2528
const client2 = createBirpc<AliceFunctions, BobFunctions>(
2629
Bob,
2730
{
2831
post: data => channel2.port1.postMessage(data),
2932
on: fn => channel2.port1.on('message', fn),
33+
meta: {
34+
name: 'client2',
35+
},
3036
},
3137
)
3238
const client3 = createBirpc<AliceFunctions, BobFunctions>(
3339
Bob,
3440
{
3541
post: data => channel3.port1.postMessage(data),
3642
on: fn => channel3.port1.on('message', fn),
43+
meta: {
44+
name: 'client3',
45+
},
3746
},
3847
)
3948

@@ -43,18 +52,31 @@ it('group', async () => {
4352
{
4453
post: data => channel1.port2.postMessage(data),
4554
on: fn => channel1.port2.on('message', fn),
55+
meta: {
56+
name: 'channel1',
57+
},
4658
},
4759
{
4860
post: data => channel2.port2.postMessage(data),
4961
on: fn => channel2.port2.on('message', fn),
62+
meta: {
63+
name: 'channel2',
64+
},
5065
},
5166
],
52-
{ eventNames: ['bump'] },
67+
{
68+
eventNames: ['bump'],
69+
resolver(name, fn): any {
70+
if (name === 'hello' && this.$meta?.name === 'channel1')
71+
return async (name: string) => `${await fn(name)} (from channel1)`
72+
return fn
73+
},
74+
},
5375
)
5476

5577
// RPCs
5678
expect(await client1.hello('Bob'))
57-
.toEqual('Hello Bob, my name is Alice')
79+
.toEqual('Hello Bob, my name is Alice (from channel1)')
5880
expect(await client2.hello('Bob'))
5981
.toEqual('Hello Bob, my name is Alice')
6082
expect(await server.broadcast.hi('Alice'))

test/hook.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ const mockFn = {
1414
}
1515

1616
function createChannel(options: {
17-
onRequest?: EventOptions<BobFunctions>['onRequest']
17+
onRequest?: EventOptions<BobFunctions, AliceFunctions>['onRequest']
1818
} = {}) {
1919
const channel = new MessageChannel()
20-
const { onRequest = () => {} } = options
20+
const {
21+
onRequest = () => {},
22+
} = options
2123
return {
2224
channel,
2325
alice: createBirpc<BobFunctions, AliceFunctions>(

0 commit comments

Comments
 (0)