Skip to content

Commit c549a2f

Browse files
authored
fix: SharedWorker not working on some mobile browsers (#336)
1 parent cb17b83 commit c549a2f

File tree

3 files changed

+243
-15
lines changed

3 files changed

+243
-15
lines changed

src/socket/io.worker.ts

+19-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Socket } from 'socket.io-client'
77

88
let ws: Socket | null = null
99

10-
function setupIo(config: { url: string }) {
10+
function setupIo(config: { url: string; socket_session_id: string }) {
1111
if (ws) return
1212
// 使用 socket.io
1313
console.log('Connecting to io, url: ', config.url)
@@ -18,6 +18,10 @@ function setupIo(config: { url: string }) {
1818
autoConnect: false,
1919
reconnectionAttempts: 3,
2020
transports: ['websocket'],
21+
22+
query: {
23+
socket_session_id: config.socket_session_id,
24+
},
2125
})
2226
if (!ws) return
2327

@@ -65,13 +69,7 @@ function setupIo(config: { url: string }) {
6569

6670
const ports = [] as MessagePort[]
6771

68-
self.addEventListener('connect', (ev: any) => {
69-
const event = ev as MessageEvent
70-
71-
const port = event.ports[0]
72-
73-
ports.push(port)
74-
72+
const preparePort = (port: MessagePort | Window) => {
7573
port.onmessage = (event) => {
7674
const { type, payload } = event.data
7775
console.log('get message from main', event.data)
@@ -101,10 +99,23 @@ self.addEventListener('connect', (ev: any) => {
10199
console.log('Unknown message type:', type)
102100
}
103101
}
102+
}
104103

104+
self.addEventListener('connect', (ev: any) => {
105+
const event = ev as MessageEvent
106+
107+
const port = event.ports[0]
108+
109+
ports.push(port)
110+
preparePort(port)
105111
port.start()
106112
})
107113

114+
if (!('SharedWorkerGlobalScope' in self)) {
115+
ports.push(self as any as MessagePort)
116+
preparePort(self)
117+
}
118+
108119
function boardcast(payload: any) {
109120
console.log('[ws] boardcast', payload)
110121
ports.forEach((port) => {

src/socket/worker-client.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.
44

55
import { simpleCamelcaseKeys as camelcaseKeys } from '@mx-space/api-client'
66

7+
import { getSocketWebSessionId } from '~/atoms/hooks'
78
import { setSocketIsConnect } from '~/atoms/socket'
89
import { GATEWAY_URL } from '~/constants/env'
910
import { SocketConnectedEvent, SocketDisconnectedEvent } from '~/events'
1011
import { isDev, isServerSide } from '~/lib/env'
1112

1213
import { eventHandler } from './handler'
14+
import { SharedWorkerPolyfill as SharedWorker } from './worker-polyfill'
1315

1416
interface WorkerSocket {
1517
sid: string
@@ -48,13 +50,13 @@ class SocketWorker {
4850
}
4951
}
5052
bindMessageHandler = (worker: SharedWorker) => {
51-
worker.port.onmessage = (event: MessageEvent) => {
53+
worker.onmessage = (event: MessageEvent) => {
5254
const { data } = event
5355
const { type, payload } = data
5456

5557
switch (type) {
5658
case 'ping': {
57-
worker?.port.postMessage({
59+
worker?.postMessage({
5860
type: 'pong',
5961
})
6062
console.log('[ws worker] pong')
@@ -99,17 +101,18 @@ class SocketWorker {
99101
prepare(worker: SharedWorker) {
100102
const gatewayUrlWithoutTrailingSlash = GATEWAY_URL.replace(/\/$/, '')
101103
this.bindMessageHandler(worker)
102-
worker.port.postMessage({
104+
worker.postMessage({
103105
type: 'config',
104106

105107
payload: {
106108
url: `${gatewayUrlWithoutTrailingSlash}/web`,
109+
socket_session_id: getSocketWebSessionId(),
107110
},
108111
})
109112

110-
worker.port.start()
113+
worker.start()
111114

112-
worker.port.postMessage({
115+
worker.postMessage({
113116
type: 'init',
114117
})
115118
}
@@ -125,14 +128,14 @@ class SocketWorker {
125128
}
126129

127130
emit(event: SocketEmitEnum, payload: any) {
128-
this.worker?.port.postMessage({
131+
this.worker?.postMessage({
129132
type: 'emit',
130133
payload: { type: event, payload },
131134
})
132135
}
133136

134137
reconnect() {
135-
this.worker?.port.postMessage({
138+
this.worker?.postMessage({
136139
type: 'reconnect',
137140
})
138141
}

src/socket/worker-polyfill.ts

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copy from https://github.com/okikio/sharedworker/blob/31830ea0f1f4b1d1cf1444aee7fb1ffd832f63e3/src/index.ts, which is licensed under the MIT license.
2+
3+
// Adapted from https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts, which is licensed under the MIT license.
4+
// If the above file is removed or modified, you can access the original state in the following GitHub Gist: https://gist.github.com/okikio/6809cfc0cdbf1df4c0573addaaf7e259
5+
6+
/**
7+
* A polyfill class for `SharedWorker`, it accepts a URL/string as well as any other options the spec. allows for `SharedWorker`. It supports all the same methods and properties as the original, except it adds compatibility methods and properties for older browsers that don't support `SharedWorker`, so, it can switch to normal `Workers` instead.
8+
*/
9+
export class SharedWorkerPolyfill
10+
implements SharedWorker, EventTarget, AbstractWorker
11+
{
12+
/**
13+
* The actual worker that is used, depending on browser support it can be either a `SharedWorker` or a normal `Worker`.
14+
*/
15+
public ActualWorker: SharedWorker | Worker
16+
constructor(url: string | URL, opts?: WorkerOptions) {
17+
if ('SharedWorker' in window) {
18+
this.ActualWorker = new SharedWorker(url, opts)
19+
} else {
20+
this.ActualWorker = new Worker(url, opts)
21+
}
22+
}
23+
24+
/**
25+
* An EventListener called when MessageEvent of type message is fired on the port—that is, when the port receives a message.
26+
*/
27+
public get onmessage() {
28+
if ('SharedWorker' in window) {
29+
return (this.ActualWorker as SharedWorker)?.port.onmessage
30+
} else {
31+
return (this.ActualWorker as Worker)
32+
.onmessage as unknown as MessagePort['onmessage']
33+
}
34+
}
35+
36+
public set onmessage(value: MessagePort['onmessage'] | Worker['onmessage']) {
37+
if ('SharedWorker' in window) {
38+
;(this.ActualWorker as SharedWorker).port.onmessage =
39+
value as MessagePort['onmessage']
40+
} else {
41+
;(this.ActualWorker as Worker).onmessage = value as Worker['onmessage']
42+
}
43+
}
44+
45+
/**
46+
* An EventListener called when a MessageEvent of type MessageError is fired—that is, when it receives a message that cannot be deserialized.
47+
*/
48+
public get onmessageerror() {
49+
if ('SharedWorker' in window) {
50+
return (this.ActualWorker as SharedWorker)?.port.onmessageerror
51+
} else {
52+
return (this.ActualWorker as Worker).onmessageerror
53+
}
54+
}
55+
56+
public set onmessageerror(
57+
value: MessagePort['onmessageerror'] | Worker['onmessageerror'],
58+
) {
59+
if ('SharedWorker' in window) {
60+
;(this.ActualWorker as SharedWorker).port.onmessageerror =
61+
value as MessagePort['onmessageerror']
62+
} else {
63+
;(this.ActualWorker as Worker).onmessageerror =
64+
value as Worker['onmessageerror']
65+
}
66+
}
67+
68+
/**
69+
* Starts the sending of messages queued on the port (only needed when using EventTarget.addEventListener; it is implied when using MessagePort.onmessage.)
70+
*/
71+
public start() {
72+
if ('SharedWorker' in window) {
73+
return (this.ActualWorker as SharedWorker)?.port.start()
74+
}
75+
}
76+
77+
/**
78+
* Clones message and transmits it to worker's global environment. transfer can be passed as a list of objects that are to be transferred rather than cloned.
79+
*/
80+
public postMessage(
81+
message: any,
82+
transfer?: Transferable[] | StructuredSerializeOptions,
83+
) {
84+
if ('SharedWorker' in window) {
85+
return (this.ActualWorker as SharedWorker)?.port.postMessage(
86+
message,
87+
transfer as Transferable[],
88+
)
89+
} else {
90+
return (this.ActualWorker as Worker).postMessage(
91+
message,
92+
transfer as Transferable[],
93+
)
94+
}
95+
}
96+
97+
/**
98+
* Immediately terminates the worker. This does not let worker finish its operations; it is halted at once. ServiceWorker instances do not support this method.
99+
*/
100+
public terminate() {
101+
if ('SharedWorker' in window) {
102+
return (this.ActualWorker as SharedWorker)?.port.close()
103+
} else {
104+
return (this.ActualWorker as Worker).terminate()
105+
}
106+
}
107+
108+
/**
109+
* Disconnects the port, so it is no longer active.
110+
*/
111+
public close() {
112+
return this.terminate()
113+
}
114+
115+
/**
116+
* Returns a MessagePort object used to communicate with and control the shared worker.
117+
*/
118+
public get port() {
119+
return (
120+
'SharedWorker' in window
121+
? (this.ActualWorker as SharedWorker).port
122+
: this.ActualWorker
123+
) as MessagePort
124+
}
125+
126+
/**
127+
* Is an EventListener that is called whenever an ErrorEvent of type error event occurs.
128+
*/
129+
public get onerror() {
130+
return this.ActualWorker.onerror
131+
}
132+
public set onerror(
133+
value: ((this: AbstractWorker, ev: ErrorEvent) => any) | null,
134+
) {
135+
this.ActualWorker.onerror = value
136+
}
137+
138+
/**
139+
* Registers an event handler of a specific event type on the EventTarget
140+
*/
141+
public addEventListener<K extends keyof WorkerEventMap>(
142+
type: K,
143+
listener: (this: Worker, ev: WorkerEventMap[K]) => any,
144+
options?: boolean | AddEventListenerOptions,
145+
): void
146+
public addEventListener(
147+
type: string,
148+
listener: EventListenerOrEventListenerObject,
149+
options?: boolean | AddEventListenerOptions,
150+
): void
151+
public addEventListener<K extends keyof MessagePortEventMap>(
152+
type: K,
153+
listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any,
154+
options?: boolean | AddEventListenerOptions,
155+
): void
156+
public addEventListener(
157+
type: string,
158+
listener: EventListenerOrEventListenerObject,
159+
options?: boolean | AddEventListenerOptions,
160+
): void {
161+
if ('SharedWorker' in window && type !== 'error') {
162+
return (this.ActualWorker as SharedWorker)?.port.addEventListener(
163+
type,
164+
listener,
165+
options,
166+
)
167+
} else {
168+
return this.ActualWorker.addEventListener(type, listener, options)
169+
}
170+
}
171+
172+
/**
173+
* Removes an event listener from the EventTarget.
174+
*/
175+
public removeEventListener<K extends keyof WorkerEventMap>(
176+
type: K,
177+
listener: (this: Worker, ev: WorkerEventMap[K]) => any,
178+
options?: boolean | EventListenerOptions,
179+
): void
180+
public removeEventListener(
181+
type: string,
182+
listener: EventListenerOrEventListenerObject,
183+
options?: boolean | EventListenerOptions,
184+
): void
185+
public removeEventListener<K extends keyof MessagePortEventMap>(
186+
type: K,
187+
listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any,
188+
options?: boolean | EventListenerOptions,
189+
): void
190+
public removeEventListener(
191+
type: string,
192+
listener: EventListenerOrEventListenerObject,
193+
options?: boolean | EventListenerOptions,
194+
): void {
195+
if ('SharedWorker' in window && type !== 'error') {
196+
return (this.ActualWorker as SharedWorker)?.port.removeEventListener(
197+
type,
198+
listener,
199+
options,
200+
)
201+
} else {
202+
return this.ActualWorker.removeEventListener(type, listener, options)
203+
}
204+
}
205+
206+
/**
207+
* Dispatches an event to this EventTarget.
208+
*/
209+
public dispatchEvent(event: Event) {
210+
return this.ActualWorker.dispatchEvent(event)
211+
}
212+
}
213+
214+
export default SharedWorkerPolyfill

0 commit comments

Comments
 (0)