11/*
22 * The service worker streams every file through a port of its own to the MTProto worker, handed over
33 * by the first tab it counts as connected. A tab it does not count - one that answered the startup check
4- * late, one it took for gone - must still get that port, or every video hangs until the worker restarts.
4+ * late, one it took for gone - must still get that port, or every video hangs until the worker restarts;
5+ * and a window with no app in it to answer, the Document PiP window, must never get it.
56 */
67import { afterEach , beforeEach , describe , expect , test , vi } from 'vitest' ;
8+ import deferred from '@/tests/helpers/deferred' ;
79
810const mocks = vi . hoisted ( ( ) => ( {
911 invoke : vi . fn ( ) ,
@@ -59,15 +61,25 @@ vi.mock('@lib/passcode/deferredIsUsingPasscode', () => ({default: {
5961} } ) ) ;
6062
6163const tab = { id : 'tab-1' , type : 'window' , frameType : 'top-level' } ;
64+ // * the Document PiP window the app moves into: a window of the origin, with no app of its own to answer
65+ const pip = { id : 'pip-1' , type : 'window' , frameType : 'auxiliary' } ;
6266
63- const startWorker = ( ) => import ( '@lib/serviceWorker/index.service' ) ;
67+ const startWorker = async ( ) => {
68+ await import ( '@lib/serviceWorker/index.service' ) ;
69+ await vi . advanceTimersByTimeAsync ( 0 ) ;
70+ } ;
6471
65- const fetchFromTab = ( clientId : string ) => ( self as any ) . onfetch ( {
72+ const fetchFrom = ( client : { id : string } ) => ( self as any ) . onfetch ( {
6673 request : { url : 'https://web.telegram.org/k/stream/%7B%7D' , mode : 'cors' , headers : new Headers ( ) } ,
67- clientId,
74+ clientId : client . id ,
6875 respondWith : ( ) => { }
6976} ) ;
7077
78+ const answerHelloFrom = ( ...windows : any [ ] ) => mocks . invoke . mockImplementation ( ( type : string , payload : any , withAck : any , target : any ) => {
79+ return type === 'hello' && windows . includes ( target ) ? Promise . resolve ( ) : new Promise ( ( ) => { } ) ;
80+ } ) ;
81+
82+ const hellosSentTo = ( target : any ) => mocks . invoke . mock . calls . filter ( ( [ type , , , source ] ) => type === 'hello' && source === target ) ;
7183const portsSentTo = ( target : any ) => mocks . invokeVoid . mock . calls . filter ( ( [ type , , source ] ) => type === 'port' && source === target ) ;
7284
7385describe ( 'the service worker\'s MTProto port' , ( ) => {
@@ -77,40 +89,36 @@ describe('the service worker\'s MTProto port', () => {
7789 mocks . invoke . mockReset ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) ;
7890 mocks . invokeVoid . mockReset ( ) ;
7991 mocks . getWindowClients . mockReset ( ) . mockResolvedValue ( [ ] ) ;
80- ( self as any ) . clients = { get : vi . fn ( async ( id : string ) => id === tab . id ? tab : undefined ) } ;
8192 } ) ;
8293
8394 afterEach ( ( ) => {
8495 vi . useRealTimers ( ) ;
85- delete ( self as any ) . clients ;
8696 delete ( self as any ) . onfetch ;
8797 } ) ;
8898
8999 test ( 'goes to a tab that answers the startup check late' , async ( ) => {
90- let answer : ( ) => void ;
91- mocks . invoke . mockImplementation ( ( type : string ) => type === 'hello' ?
92- new Promise < void > ( ( resolve ) => answer = resolve ) :
93- new Promise ( ( ) => { } ) ) ;
100+ const answer = deferred ( ) ;
101+ mocks . invoke . mockImplementation ( ( type : string ) => type === 'hello' ? answer . promise : new Promise ( ( ) => { } ) ) ;
94102 mocks . getWindowClients . mockResolvedValue ( [ tab ] ) ;
95103
96104 await startWorker ( ) ;
97- await vi . advanceTimersByTimeAsync ( 0 ) ;
98- expect ( mocks . invoke ) . toHaveBeenCalledWith ( 'hello' , undefined , undefined , tab ) ;
105+ expect ( hellosSentTo ( tab ) ) . toHaveLength ( 1 ) ;
99106
100107 // * the tab is busy or frozen while the worker starts, and answers well after it
101108 await vi . advanceTimersByTimeAsync ( 30e3 ) ;
102109 expect ( portsSentTo ( tab ) ) . toHaveLength ( 0 ) ;
103- answer ( ) ;
110+ answer . resolve ( ) ;
104111 await vi . advanceTimersByTimeAsync ( 0 ) ;
105112
106113 expect ( portsSentTo ( tab ) , 'the late answer was dropped' ) . toHaveLength ( 1 ) ;
107114 } ) ;
108115
109116 test ( 'goes to a tab the worker does not count with its next request' , async ( ) => {
110117 await startWorker ( ) ;
111- await vi . advanceTimersByTimeAsync ( 0 ) ;
118+ answerHelloFrom ( tab ) ;
119+ mocks . getWindowClients . mockResolvedValue ( [ tab ] ) ;
112120
113- fetchFromTab ( tab . id ) ;
121+ fetchFrom ( tab ) ;
114122 await vi . advanceTimersByTimeAsync ( 0 ) ;
115123
116124 expect ( portsSentTo ( tab ) , 'the request hangs with no port' ) . toHaveLength ( 1 ) ;
@@ -119,30 +127,43 @@ describe('the service worker\'s MTProto port', () => {
119127
120128 test ( 'costs a connected tab\'s requests nothing and is not sent twice' , async ( ) => {
121129 await startWorker ( ) ;
130+ answerHelloFrom ( tab ) ;
131+ mocks . getWindowClients . mockResolvedValue ( [ tab ] ) ;
132+ fetchFrom ( tab ) ;
122133 await vi . advanceTimersByTimeAsync ( 0 ) ;
123- fetchFromTab ( tab . id ) ;
124- await vi . advanceTimersByTimeAsync ( 0 ) ;
125- const getClient = ( self as any ) . clients . get ;
126- getClient . mockClear ( ) ;
134+ mocks . getWindowClients . mockClear ( ) ;
127135
128- fetchFromTab ( tab . id ) ;
129- fetchFromTab ( tab . id ) ;
136+ fetchFrom ( tab ) ;
137+ fetchFrom ( tab ) ;
130138 await vi . advanceTimersByTimeAsync ( 0 ) ;
131139
132- expect ( getClient ) . not . toHaveBeenCalled ( ) ;
140+ expect ( mocks . getWindowClients ) . not . toHaveBeenCalled ( ) ;
133141 expect ( portsSentTo ( tab ) ) . toHaveLength ( 1 ) ;
134142 } ) ;
135143
136- test ( 'is not handed to anything but a window' , async ( ) => {
137- const worker = { id : 'worker-1' , type : 'sharedworker' } ;
138- ( self as any ) . clients . get = vi . fn ( async ( ) => worker ) ;
144+ test ( 'goes to the tab whose app answers, not to the PiP window the request comes from' , async ( ) => {
139145 await startWorker ( ) ;
146+ answerHelloFrom ( tab ) ;
147+ mocks . getWindowClients . mockResolvedValue ( [ pip , tab ] ) ;
148+
149+ fetchFrom ( pip ) ;
140150 await vi . advanceTimersByTimeAsync ( 0 ) ;
141151
142- fetchFromTab ( worker . id ) ;
143- fetchFromTab ( '' ) ;
152+ expect ( portsSentTo ( pip ) , 'nobody reads the port there' ) . toHaveLength ( 0 ) ;
153+ expect ( portsSentTo ( tab ) ) . toHaveLength ( 1 ) ;
154+ } ) ;
155+
156+ test ( 'asks a window once while its answer is awaited' , async ( ) => {
157+ await startWorker ( ) ;
158+ mocks . getWindowClients . mockResolvedValue ( [ pip ] ) ;
159+
160+ fetchFrom ( pip ) ;
161+ await vi . advanceTimersByTimeAsync ( 0 ) ;
162+ fetchFrom ( pip ) ;
163+ fetchFrom ( pip ) ;
144164 await vi . advanceTimersByTimeAsync ( 0 ) ;
145165
166+ expect ( hellosSentTo ( pip ) ) . toHaveLength ( 1 ) ;
146167 expect ( mocks . invokeVoid . mock . calls . filter ( ( [ type ] ) => type === 'port' ) ) . toHaveLength ( 0 ) ;
147168 } ) ;
148169} ) ;
0 commit comments