diff --git a/packages/dispatcher/src/dispatcher.ts b/packages/dispatcher/src/dispatcher.ts index 1906d49d..8ae5bf77 100644 --- a/packages/dispatcher/src/dispatcher.ts +++ b/packages/dispatcher/src/dispatcher.ts @@ -90,6 +90,10 @@ export interface DispatcherParams { key?: StateKeyDelegate } +export interface DispatcherDependencies { + // intentionally empty, to be extended by consumers +} + /** * Updates dispatcher */ @@ -112,6 +116,8 @@ export class Dispatcher { private _customStateKeyDelegate?: StateKeyDelegate private _customStorage?: StateService + private _deps: DispatcherDependencies = {} + private _errorHandler?: ( err: Error, update: ParsedUpdate & T, @@ -193,6 +199,26 @@ export class Dispatcher { return this._scene } + /** + * Inject a dependency to be available in this dispatcher and all its children. + * + * **Note**: This is only available for the root dispatcher. + */ + inject(name: Name, value: DispatcherDependencies[Name]): void { + if (this._parent) { + throw new MtArgumentError('Cannot inject dependencies to child dispatchers') + } + + this._deps[name] = value + } + + /** + * Get the dependencies injected into this dispatcher. + */ + get deps(): DispatcherDependencies { + return this._deps + } + /** * Bind the dispatcher to the client. * Called by the constructor automatically if @@ -678,6 +704,7 @@ export class Dispatcher { child._parent = this as any child._client = this._client child._storage = this._storage + child._deps = this._deps child._stateKeyDelegate = this._stateKeyDelegate child._customStorage ??= this._customStorage child._customStateKeyDelegate ??= this._customStateKeyDelegate @@ -773,8 +800,9 @@ export class Dispatcher { private _unparent(): void { this._parent = this._client = undefined - ;(this as any)._stateKeyDelegate = undefined - ;(this as any)._storage = undefined + this._deps = {} // to avoid dangling references + this._stateKeyDelegate = undefined + this._storage = undefined } /** diff --git a/packages/dispatcher/src/filters/bots.test.ts b/packages/dispatcher/src/filters/bots.test.ts index 6982af5a..9541fbf6 100644 --- a/packages/dispatcher/src/filters/bots.test.ts +++ b/packages/dispatcher/src/filters/bots.test.ts @@ -12,6 +12,7 @@ peers.chats.set(1, createStub('channel', { id: 1 })) const createMessageContext = (partial: Partial) => new MessageContext( StubTelegramClient.full(), // eslint-disable-line + {}, new Message(createStub('message', partial), peers, false), ) diff --git a/packages/dispatcher/tests/dispatcher.test.ts b/packages/dispatcher/tests/dispatcher.test.ts index e218c6ff..6ec7a0f2 100644 --- a/packages/dispatcher/tests/dispatcher.test.ts +++ b/packages/dispatcher/tests/dispatcher.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest' -import { PeersIndex } from '@mtcute/core' +import { Message, PeersIndex } from '@mtcute/core' import { TelegramClient } from '@mtcute/core/client.js' -import { StubTelegramClient } from '@mtcute/test' +import { createStub, StubTelegramClient } from '@mtcute/test' import { Dispatcher, PropagationAction } from '../src/index.js' @@ -237,4 +237,41 @@ describe('Dispatcher', () => { ]) }) }) + + describe('Dependency injection', () => { + it('should inject dependencies into update contexts', async () => { + const dp = Dispatcher.for(client) + + dp.inject('foo' as never, 'foo' as never) + + const log: string[] = [] + + dp.onNewMessage(() => { + // eslint-disable-next-line + log.push(`received ${(dp.deps as any).foo}`) + }) + + const dp2 = Dispatcher.child() + + dp2.onNewMessage(() => { + // eslint-disable-next-line + log.push(`received ${(dp.deps as any).foo} (child)`) + }) + + dp.addChild(dp2) + + await dp.dispatchUpdateNow({ + name: 'new_message', + data: new Message( + createStub('message'), + emptyPeers, + ), + }) + + expect(log).eql([ + 'received foo', + 'received foo (child)', + ]) + }) + }) }) diff --git a/packages/i18n/tests/i18n.test.ts b/packages/i18n/tests/i18n.test.ts index 35d98278..c9556f14 100644 --- a/packages/i18n/tests/i18n.test.ts +++ b/packages/i18n/tests/i18n.test.ts @@ -85,6 +85,7 @@ describe('i18n', () => { it('should parse language from a message', () => { const message = new MessageContext( null as never, + {}, new Message( { _: 'message', peerId: { _: 'peerUser', userId: 1 } } as never, PeersIndex.from({