From 3f069dfc9c1975fb2463c2f5bb66308d331cc4b2 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Sun, 6 Sep 2026 15:40:50 -0400 Subject: [PATCH] refactor(mobile-web): put the speech ledger on the shared subscription base The speech ledger was the one shell ledger still hand-rolling its record map, cancellation and posting because it is push-driven. It maps onto MobileWebSubscriptionLedger with no base-class change: admit plus a records.set stands in for open, and enqueue carries the broadcast. The authority now takes a MobileWebSubscriptionLedgerConfig, which retires the per-subscription post/closed plumbing, and the broker hands all three consumers one shared subscriptionPosts() sender. Speech stays out of replaceClient's closeAll on purpose: its feed is the shell's device runtime, not the host RPC client, so it survives the swap and a terminal closure frame would wedge the dictation hook in error with no resubscribe. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- .../mobile-web-broker-message-sender.ts | 11 +++ .../mobile-web-capability-broker.ts | 16 ++-- .../mobile-web-capability-execution-arms.ts | 4 +- ...e-web-capability-execution-dependencies.ts | 8 -- .../mobile-web-capability-subscriptions.ts | 30 +++---- .../mobile-web-speech-authority.test.ts | 37 ++++----- .../mobile-web/mobile-web-speech-authority.ts | 19 ++--- .../mobile-web-speech-subscriptions.test.ts | 50 +++++++----- .../mobile-web-speech-subscriptions.ts | 80 +++++-------------- ...le-web-subscription-closure-frames.test.ts | 14 ++++ ...-web-subscription-ledger-lifecycle.test.ts | 16 ++-- 11 files changed, 128 insertions(+), 157 deletions(-) diff --git a/mobile/src/mobile-web/mobile-web-broker-message-sender.ts b/mobile/src/mobile-web/mobile-web-broker-message-sender.ts index f58ce73f603..b22755b7a2a 100644 --- a/mobile/src/mobile-web/mobile-web-broker-message-sender.ts +++ b/mobile/src/mobile-web/mobile-web-broker-message-sender.ts @@ -4,6 +4,8 @@ import type { MobileWebBridgeShellMessage } from '../../../src/shared/mobile-web/bridge-contract' import { mobileWebBrokerEnvelope } from './mobile-web-broker-envelope' +import { mobileWebSubscriptionClosedPoster } from './mobile-web-subscription-closure' +import type { MobileWebSubscriptionLedgerConfig } from './mobile-web-subscription-ledger' export class MobileWebBrokerMessageSender { constructor( @@ -57,6 +59,15 @@ export class MobileWebBrokerMessageSender { }) } + /** A ledger only ever needs this channel's liveness, its event frame, and its closure frame. */ + subscriptionPosts(): MobileWebSubscriptionLedgerConfig { + return { + isActive: this.options.isActive, + postEvent: (subscriptionId, sequence, event) => this.event(subscriptionId, sequence, event), + postClosed: mobileWebSubscriptionClosedPoster(this) + } + } + private async post(message: MobileWebBridgeShellMessage): Promise { if (this.options.isActive()) { await this.options.postMessage(message) diff --git a/mobile/src/mobile-web/mobile-web-capability-broker.ts b/mobile/src/mobile-web/mobile-web-capability-broker.ts index 2cdee3e02fa..8630e2326fb 100644 --- a/mobile/src/mobile-web/mobile-web-capability-broker.ts +++ b/mobile/src/mobile-web/mobile-web-capability-broker.ts @@ -21,7 +21,6 @@ import { MobileWebCapabilityAuthorities } from './mobile-web-capability-authorit import type { MobileWebCapabilityBrokerOptions } from './mobile-web-capability-broker-options' import { MobileWebBrokerMessageSender } from './mobile-web-broker-message-sender' import { MobileWebBrokerReplayGuard } from './mobile-web-broker-replay-guard' -import { mobileWebSubscriptionClosedPoster } from './mobile-web-subscription-closure' import { rememberMobileWebBrokerRoute } from './mobile-web-broker-route-memory' import { resolveMobileWebHostNavigationRoute } from './mobile-web-host-navigation-route' import { @@ -46,7 +45,7 @@ export class MobileWebCapabilityBroker { private readonly replay = new MobileWebBrokerReplayGuard() private readonly subscriptions: MobileWebCapabilitySubscriptions private readonly terminalStreams: MobileWebTerminalStreams - private readonly speechAuthority = new MobileWebSpeechAuthority() + private readonly speechAuthority: MobileWebSpeechAuthority private readonly rateLimiter: MobileWebOperationRateLimiter private readonly commitMessageGeneration = new MobileWebCommitMessageGeneration() private readonly authorities: MobileWebCapabilityAuthorities @@ -61,23 +60,22 @@ export class MobileWebCapabilityBroker { isActive: () => !this.disposed && options.isActive(), postMessage: options.postMessage }) + const posts = this.messages.subscriptionPosts() this.subscriptions = new MobileWebCapabilitySubscriptions({ - isActive: () => !this.disposed && this.options.isActive(), - messages: this.messages, + ...posts, browserAuthority: this.authorities.browser, nativeChatAuthority: this.authorities.nativeChat, workspaceAuthority: this.authorities.workspace }) this.terminalStreams = new MobileWebTerminalStreams({ - isActive: () => !this.disposed && this.options.isActive(), + ...posts, clientId: options.terminalClientId, now: options.now, onFlowMetrics: options.onTerminalFlowMetrics, onResync: options.onTerminalResync, - workspaceAuthority: this.authorities.workspace, - postEvent: this.messages.event.bind(this.messages), - postClosed: mobileWebSubscriptionClosedPoster(this.messages) + workspaceAuthority: this.authorities.workspace }) + this.speechAuthority = new MobileWebSpeechAuthority(posts) } async handle(message: MobileWebBridgePageMessage): Promise { @@ -245,8 +243,6 @@ export class MobileWebCapabilityBroker { sourceControlSubscriptions: this.subscriptions.sourceControl, sourceControlBranchCompare: this.authorities.sourceControlBranchCompare, speechAuthority: this.speechAuthority, - postSpeechEvent: this.messages.event.bind(this.messages), - postSpeechClosed: mobileWebSubscriptionClosedPoster(this.messages), workspaceSubscriptions: this.subscriptions.workspace, terminalStreams: this.terminalStreams, commitMessageGeneration: this.commitMessageGeneration, diff --git a/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts b/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts index 1e03cf57d9d..bbca252abe9 100644 --- a/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts +++ b/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts @@ -271,9 +271,7 @@ async function subscribeSpeech(args: Deps, request: SubscriptionRequest): Promis MobileWebSpeechSubscribePayloadSchema.parse(request.payload) args.speechAuthority.subscribe({ requestId: request.requestId, - subscriptionId: request.subscriptionId, - post: (sequence, event) => args.postSpeechEvent(request.subscriptionId, sequence, event), - closed: (closure) => args.postSpeechClosed(request.subscriptionId, closure) + subscriptionId: request.subscriptionId }) return null } diff --git a/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts b/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts index 03bef266d15..7f1a50fdc38 100644 --- a/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts +++ b/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts @@ -1,6 +1,4 @@ -import type { MobileWebPostSubscriptionClosed } from './mobile-web-subscription-closure' import type { MobileWebBridgePageMessage } from '../../../src/shared/mobile-web/bridge-contract' -import type { MobileWebSpeechEvent } from '../../../src/shared/mobile-web/speech-operation-contract' import type { RpcClient } from '../transport/rpc-client' import type { MobileWebAccountSubscriptions } from './mobile-web-account-subscriptions' import type { MobileWebAgentHistoryAuthority } from './mobile-web-agent-history-authority' @@ -43,12 +41,6 @@ export type MobileWebCapabilityExecutionDependencies = { sourceControlSubscriptions: MobileWebSourceControlSubscriptions sourceControlBranchCompare: MobileWebSourceControlBranchComparePager speechAuthority: MobileWebSpeechAuthority - postSpeechEvent: ( - subscriptionId: string, - sequence: number, - event: MobileWebSpeechEvent - ) => Promise - postSpeechClosed: MobileWebPostSubscriptionClosed workspaceSubscriptions: MobileWebWorkspaceSubscriptions terminalStreams: MobileWebTerminalStreams commitMessageGeneration: MobileWebCommitMessageGeneration diff --git a/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts b/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts index 2a44b62becb..476cab8942e 100644 --- a/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts +++ b/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts @@ -1,10 +1,11 @@ import { MobileWebAccountSubscriptions } from './mobile-web-account-subscriptions' -import { mobileWebSubscriptionClosedPoster } from './mobile-web-subscription-closure' import type { MobileWebSubscriptionClosure } from './mobile-web-subscription-closure' -import type { MobileWebSubscriptionLedgerHandle } from './mobile-web-subscription-ledger' +import type { + MobileWebSubscriptionLedgerConfig, + MobileWebSubscriptionLedgerHandle +} from './mobile-web-subscription-ledger' import type { MobileWebBrowserAuthority } from './mobile-web-browser-authority' import { MobileWebBrowserStreams } from './mobile-web-browser-streams' -import type { MobileWebBrokerMessageSender } from './mobile-web-broker-message-sender' import { MobileWebSessionSubscriptions } from './mobile-web-session-subscriptions' import type { MobileWebNativeChatAuthority } from './mobile-web-native-chat-authority' import { MobileWebNativeChatSubscriptions } from './mobile-web-native-chat-subscriptions' @@ -21,17 +22,18 @@ export class MobileWebCapabilitySubscriptions { readonly workspace: MobileWebWorkspaceSubscriptions private readonly ledgers: MobileWebSubscriptionLedgerHandle[] - constructor(args: { - isActive: () => boolean - messages: MobileWebBrokerMessageSender - browserAuthority: MobileWebBrowserAuthority - nativeChatAuthority: MobileWebNativeChatAuthority - workspaceAuthority: MobileWebWorkspaceAuthority - }) { - const postEvent = (subscriptionId: string, sequence: number, event: unknown) => - args.messages.event(subscriptionId, sequence, event) - const postClosed = mobileWebSubscriptionClosedPoster(args.messages) - const shared = { isActive: args.isActive, postEvent, postClosed } + constructor( + args: MobileWebSubscriptionLedgerConfig & { + browserAuthority: MobileWebBrowserAuthority + nativeChatAuthority: MobileWebNativeChatAuthority + workspaceAuthority: MobileWebWorkspaceAuthority + } + ) { + const shared = { + isActive: args.isActive, + postEvent: args.postEvent, + postClosed: args.postClosed + } this.account = new MobileWebAccountSubscriptions(shared) this.browser = new MobileWebBrowserStreams({ ...shared, diff --git a/mobile/src/mobile-web/mobile-web-speech-authority.test.ts b/mobile/src/mobile-web/mobile-web-speech-authority.test.ts index ab5c5dd920a..d10f8291698 100644 --- a/mobile/src/mobile-web/mobile-web-speech-authority.test.ts +++ b/mobile/src/mobile-web/mobile-web-speech-authority.test.ts @@ -7,15 +7,7 @@ import type { MobileWebSpeechRuntime } from './mobile-web-speech-runtime' describe('MobileWebSpeechAuthority', () => { it('keeps PCM in the shell and finishes without cancelling a successful transcript', async () => { const harness = createHarness() - const events: unknown[] = [] - harness.authority.subscribe({ - requestId: 'request-1', - subscriptionId: 'subscription-1', - post: async (sequence, event) => { - events.push({ sequence, event }) - }, - closed: vi.fn() - }) + harness.authority.subscribe({ requestId: 'request-1', subscriptionId: 'subscription-1' }) harness.sendRequest.mockImplementation(async (method) => { if (method === 'speech.dictation.finish') { return success({ text: ` ${'a'.repeat(40_000)} ` }) @@ -40,10 +32,10 @@ describe('MobileWebSpeechAuthority', () => { harness.sendRequest.mock.calls.filter(([method]) => method === 'speech.dictation.cancel') ).toHaveLength(0) await vi.waitFor(() => - expect(events).toEqual([ - { sequence: 0, event: { status: 'recording' } }, - { sequence: 1, event: { status: 'processing' } }, - { sequence: 2, event: { status: 'idle' } } + expect(harness.postEvent.mock.calls).toEqual([ + ['subscription-1', 0, { status: 'recording' }], + ['subscription-1', 1, { status: 'processing' }], + ['subscription-1', 2, { status: 'idle' }] ]) ) expect(harness.runtime.releaseKeepAwake).toHaveBeenCalledOnce() @@ -114,13 +106,7 @@ describe('MobileWebSpeechAuthority', () => { it('cancels recording when the native audio session is interrupted', async () => { const harness = createHarness() - const post = vi.fn(async () => {}) - harness.authority.subscribe({ - requestId: 'request-1', - subscriptionId: 'subscription-1', - post, - closed: vi.fn() - }) + harness.authority.subscribe({ requestId: 'request-1', subscriptionId: 'subscription-1' }) harness.sendRequest.mockResolvedValue(success({})) await harness.authority.start(harness.client) @@ -133,7 +119,7 @@ describe('MobileWebSpeechAuthority', () => { ) ) await vi.waitFor(() => - expect(post).toHaveBeenLastCalledWith(1, { + expect(harness.postEvent).toHaveBeenLastCalledWith('subscription-1', 1, { status: 'idle', reason: 'interrupted' }) @@ -187,11 +173,18 @@ function createHarness() { } satisfies MobileWebSpeechRuntime const sendRequest = vi.fn() const client = { sendRequest } as unknown as RpcClient + const postEvent = vi.fn(async () => {}) + const postClosed = vi.fn() return { - authority: new MobileWebSpeechAuthority(async () => runtime), + authority: new MobileWebSpeechAuthority( + { isActive: () => true, postEvent, postClosed }, + async () => runtime + ), runtime, client, sendRequest, + postEvent, + postClosed, get microphone() { return microphone }, diff --git a/mobile/src/mobile-web/mobile-web-speech-authority.ts b/mobile/src/mobile-web/mobile-web-speech-authority.ts index d3afe88b43a..b1704d9c700 100644 --- a/mobile/src/mobile-web/mobile-web-speech-authority.ts +++ b/mobile/src/mobile-web/mobile-web-speech-authority.ts @@ -1,4 +1,4 @@ -import type { MobileWebSubscriptionClosure } from './mobile-web-subscription-closure' +import type { MobileWebSubscriptionLedgerConfig } from './mobile-web-subscription-ledger' import type { MobileWebSpeechEvent, MobileWebSpeechStartResult, @@ -20,7 +20,7 @@ import { MobileWebSpeechSubscriptions } from './mobile-web-speech-subscriptions' export class MobileWebSpeechAuthority { private readonly audio = new MobileWebSpeechAudioForwarder() - private readonly subscriptions = new MobileWebSpeechSubscriptions() + private readonly subscriptions: MobileWebSpeechSubscriptions private runtime: MobileWebSpeechRuntime | null = null private runtimePromise: Promise | null = null private removeMicrophoneListener: (() => void) | null = null @@ -32,19 +32,14 @@ export class MobileWebSpeechAuthority { private disposed = false constructor( + config: MobileWebSubscriptionLedgerConfig, private readonly loadRuntime: () => Promise = async () => (await import('./mobile-web-speech-native-runtime')).createMobileWebSpeechNativeRuntime() - ) {} + ) { + this.subscriptions = new MobileWebSpeechSubscriptions(config) + } - subscribe(args: { - requestId: string - subscriptionId: string - post: (sequence: number, event: MobileWebSpeechEvent) => Promise - closed: (closure: MobileWebSubscriptionClosure) => void - }): void { - if (this.disposed) { - throw new MobileWebBrokerError('invalid_request') - } + subscribe(args: { requestId: string; subscriptionId: string }): void { this.subscriptions.start(args) } diff --git a/mobile/src/mobile-web/mobile-web-speech-subscriptions.test.ts b/mobile/src/mobile-web/mobile-web-speech-subscriptions.test.ts index 21b9e1b1501..8c17762366e 100644 --- a/mobile/src/mobile-web/mobile-web-speech-subscriptions.test.ts +++ b/mobile/src/mobile-web/mobile-web-speech-subscriptions.test.ts @@ -3,21 +3,20 @@ import { MobileWebSpeechSubscriptions } from './mobile-web-speech-subscriptions' describe('MobileWebSpeechSubscriptions', () => { it('orders events and drops queued delivery after cancellation', async () => { - const subscriptions = new MobileWebSpeechSubscriptions() let releaseFirst: (() => void) | undefined - const post = vi.fn((sequence: number) => + const postEvent = vi.fn((_subscriptionId: string, sequence: number) => sequence === 0 ? new Promise((resolve) => { releaseFirst = resolve }) : Promise.resolve() ) - subscriptions.start({ - requestId: 'request-1', - subscriptionId: 'subscription-1', - post, - closed: vi.fn() + const subscriptions = new MobileWebSpeechSubscriptions({ + isActive: () => true, + postEvent, + postClosed: vi.fn() }) + subscriptions.start({ requestId: 'request-1', subscriptionId: 'subscription-1' }) subscriptions.post({ status: 'recording' }) subscriptions.post({ status: 'processing' }) @@ -27,24 +26,39 @@ describe('MobileWebSpeechSubscriptions', () => { await Promise.resolve() await Promise.resolve() - expect(post).toHaveBeenCalledOnce() - expect(post).toHaveBeenCalledWith(0, { status: 'recording' }) + expect(postEvent).toHaveBeenCalledOnce() + expect(postEvent).toHaveBeenCalledWith('subscription-1', 0, { status: 'recording' }) }) it('tells the page when a delivery failure retires the dictation stream', async () => { - const subscriptions = new MobileWebSpeechSubscriptions() - const closed = vi.fn() - subscriptions.start({ - requestId: 'request-1', - subscriptionId: 'subscription-1', - post: () => Promise.reject(new Error('post failed')), - closed + const postClosed = vi.fn() + const subscriptions = new MobileWebSpeechSubscriptions({ + isActive: () => true, + postEvent: () => Promise.reject(new Error('post failed')), + postClosed }) + subscriptions.start({ requestId: 'request-1', subscriptionId: 'subscription-1' }) subscriptions.post({ status: 'recording' }) - await vi.waitFor(() => expect(closed).toHaveBeenCalledOnce()) + await vi.waitFor(() => expect(postClosed).toHaveBeenCalledOnce()) - expect(closed).toHaveBeenCalledWith({ code: 'unavailable', retryable: true }) + expect(postClosed).toHaveBeenCalledWith('subscription-1', { + code: 'unavailable', + retryable: true + }) expect(subscriptions.cancel('subscription-1')).toBeNull() }) + + it('refuses a new dictation stream once the ledger is disposed', () => { + const subscriptions = new MobileWebSpeechSubscriptions({ + isActive: () => true, + postEvent: async () => {}, + postClosed: vi.fn() + }) + subscriptions.dispose() + + expect(() => + subscriptions.start({ requestId: 'request-1', subscriptionId: 'subscription-1' }) + ).toThrow('invalid_request') + }) }) diff --git a/mobile/src/mobile-web/mobile-web-speech-subscriptions.ts b/mobile/src/mobile-web/mobile-web-speech-subscriptions.ts index 75c25725ca7..f94c861bfcb 100644 --- a/mobile/src/mobile-web/mobile-web-speech-subscriptions.ts +++ b/mobile/src/mobile-web/mobile-web-speech-subscriptions.ts @@ -1,79 +1,35 @@ -import type { MobileWebSubscriptionClosure } from './mobile-web-subscription-closure' +import { + MobileWebSubscriptionLedger, + type MobileWebSubscriptionLedgerConfig +} from './mobile-web-subscription-ledger' import type { MobileWebSpeechEvent } from '../../../src/shared/mobile-web/speech-operation-contract' import { MobileWebBrokerError } from './mobile-web-broker-error' -type SpeechSubscriber = { - requestId: string - sequence: number - delivery: Promise - post: (sequence: number, event: MobileWebSpeechEvent) => Promise - closed: (closure: MobileWebSubscriptionClosure) => void -} - -export class MobileWebSpeechSubscriptions { - private readonly records = new Map() +/** Push-driven: the shell's own dictation runtime feeds every entry, so there is no host handle to + * open and no `closeAll` on a client swap — the authority publishes `session-replaced` instead. */ +export class MobileWebSpeechSubscriptions extends MobileWebSubscriptionLedger { private disposed = false - start(args: { - requestId: string - subscriptionId: string - post: (sequence: number, event: MobileWebSpeechEvent) => Promise - closed: (closure: MobileWebSubscriptionClosure) => void - }): void { - if (this.disposed || this.records.has(args.subscriptionId)) { + constructor(config: MobileWebSubscriptionLedgerConfig) { + super({ ...config, operationKey: 'speech.subscribe' }) + } + + start(args: { requestId: string; subscriptionId: string }): void { + if (this.disposed) { throw new MobileWebBrokerError('invalid_request') } - this.records.set(args.subscriptionId, { - requestId: args.requestId, - sequence: 0, - delivery: Promise.resolve(), - post: args.post, - closed: args.closed - }) + this.admit(args.subscriptionId) + this.records.set(args.subscriptionId, this.newRecord(args.requestId)) } post(event: MobileWebSpeechEvent): void { for (const [subscriptionId, record] of this.records) { - const sequence = record.sequence++ - record.delivery = record.delivery - .then(async () => { - if (this.records.get(subscriptionId) !== record) { - return - } - await record.post(sequence, event) - }) - .catch(() => { - if (this.records.get(subscriptionId) === record) { - this.records.delete(subscriptionId) - record.closed({ code: 'unavailable', retryable: true }) - } - }) + this.enqueue(subscriptionId, record, event) } } - cancel(subscriptionId: string): string | null { - const record = this.records.get(subscriptionId) - if (!record) { - return null - } - this.records.delete(subscriptionId) - return record.requestId - } - - cancelByRequest(requestId: string): void { - for (const [subscriptionId, record] of this.records) { - if (record.requestId === requestId) { - this.records.delete(subscriptionId) - } - } - } - - countForOperation(operationKey: string): number { - return operationKey === 'speech.subscribe' ? this.records.size : 0 - } - - dispose(): void { + override dispose(): void { this.disposed = true - this.records.clear() + super.dispose() } } diff --git a/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts b/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts index 88ec1679522..0c2eceeb295 100644 --- a/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts +++ b/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts @@ -8,6 +8,8 @@ import { MobileWebNativeChatAuthority } from './mobile-web-native-chat-authority import { MobileWebNativeChatSubscriptions } from './mobile-web-native-chat-subscriptions' import { MobileWebSessionSubscriptions } from './mobile-web-session-subscriptions' import { MobileWebSourceControlSubscriptions } from './mobile-web-source-control-subscriptions' +import { MobileWebSpeechSubscriptions } from './mobile-web-speech-subscriptions' +import type { MobileWebSpeechEvent } from '../../../src/shared/mobile-web/speech-operation-contract' import { MobileWebWorkspaceSubscriptions } from './mobile-web-workspace-subscriptions' import { mobileWebHostWorkspaceIdFromHost, @@ -210,6 +212,18 @@ const LEDGER_CASES: LedgerCase[] = [ }) return host.emit } + }, + { + name: 'speech', + // Push-driven from the shell's dictation runtime, so no host frame can be unusable. + invalidCode: null, + invalid: { status: 'bogus' }, + valid: { status: 'recording' }, + open: async (posts) => { + const subscriptions = new MobileWebSpeechSubscriptions(posts) + subscriptions.start({ requestId: 'request-1', subscriptionId: SUBSCRIPTION_ID }) + return (value) => subscriptions.post(value as MobileWebSpeechEvent) + } } ] diff --git a/mobile/src/mobile-web/mobile-web-subscription-ledger-lifecycle.test.ts b/mobile/src/mobile-web/mobile-web-subscription-ledger-lifecycle.test.ts index 85703e45ed1..0fe5ee76fe6 100644 --- a/mobile/src/mobile-web/mobile-web-subscription-ledger-lifecycle.test.ts +++ b/mobile/src/mobile-web/mobile-web-subscription-ledger-lifecycle.test.ts @@ -75,15 +75,15 @@ describe('subscription ledger teardown', () => { it('fans closeAll out across every capability ledger', () => { const messages: MobileWebBridgeShellMessage[] = [] - const subscriptions = new MobileWebCapabilitySubscriptions({ + const sender = new MobileWebBrokerMessageSender({ + context: MOBILE_WEB_BRIDGE_ROUNDTRIP_CONTEXT, isActive: () => true, - messages: new MobileWebBrokerMessageSender({ - context: MOBILE_WEB_BRIDGE_ROUNDTRIP_CONTEXT, - isActive: () => true, - postMessage: (message) => { - messages.push(message) - } - }), + postMessage: (message) => { + messages.push(message) + } + }) + const subscriptions = new MobileWebCapabilitySubscriptions({ + ...sender.subscriptionPosts(), browserAuthority: new MobileWebBrowserAuthority(randomBytes), nativeChatAuthority: new MobileWebNativeChatAuthority(randomBytes), workspaceAuthority: new MobileWebWorkspaceAuthority(randomBytes)