-
Notifications
You must be signed in to change notification settings - Fork 66
fix: improve overleaf handshake and compile-chain compatibility #352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,7 +75,7 @@ export interface EventsHandler { | |
| type ConnectionScheme = 'Alt' | 'v1' | 'v2'; | ||
|
|
||
| export class SocketIOAPI { | ||
| private scheme: ConnectionScheme = 'v1'; | ||
| private scheme: ConnectionScheme = 'v2'; | ||
| private record?: Promise<ProjectEntity>; | ||
| private _handlers: Array<EventsHandler> = []; | ||
|
|
||
|
|
@@ -289,31 +289,65 @@ export class SocketIOAPI { | |
| * @returns {Promise} | ||
| */ | ||
| async joinProject(project_id:string): Promise<ProjectEntity> { | ||
| if (this.scheme==='v2') { | ||
| try { | ||
| return await this.joinProjectByV2(); | ||
| } catch (err) { | ||
| // Old Overleaf stacks may not support auto join via handshake. | ||
| // Fallback to legacy joinProject event to keep compatibility. | ||
| this.scheme = 'v1'; | ||
| this.init(); | ||
| return this.joinProjectByV1(project_id); | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| return await this.joinProjectByV1(project_id); | ||
| } catch (err) { | ||
| if (!this.shouldFallbackToV2(err)) { | ||
| throw err; | ||
| } | ||
| // New Overleaf stacks require projectId in handshake query. | ||
| this.scheme = 'v2'; | ||
| this.init(); | ||
| return this.joinProjectByV2(); | ||
| } | ||
| } | ||
|
|
||
| private async joinProjectByV1(project_id:string): Promise<ProjectEntity> { | ||
| const timeoutPromise: Promise<ProjectEntity> = new Promise((_, reject) => { | ||
| setTimeout(() => { | ||
| reject('timeout'); | ||
| }, 5000); | ||
| }); | ||
|
|
||
| switch(this.scheme) { | ||
| case 'Alt': | ||
| case 'v1': | ||
| const joinPromise = this.emit('joinProject', {project_id}) | ||
| .then((returns:[ProjectEntity, string, number]) => { | ||
| const [project, permissionsLevel, protocolVersion] = returns; | ||
| this.record = Promise.resolve(project); | ||
| return project; | ||
| }); | ||
| const rejectPromise = new Promise((_, reject) => { | ||
| this.socket.on('connectionRejected', (err:any) => { | ||
| this.scheme = 'v2'; | ||
| reject(err.message); | ||
| }); | ||
| }); | ||
| return Promise.race([joinPromise, rejectPromise, timeoutPromise]); | ||
| case 'v2': | ||
| return Promise.race([this.record!, timeoutPromise]); | ||
| } | ||
| const joinPromise = this.emit('joinProject', {project_id}) | ||
| .then((returns:[ProjectEntity, string, number]) => { | ||
| const [project, permissionsLevel, protocolVersion] = returns; | ||
| this.record = Promise.resolve(project); | ||
| return project; | ||
| }); | ||
| const rejectPromise: Promise<ProjectEntity> = new Promise((_, reject) => { | ||
| this.socket.on('connectionRejected', (err:any) => { | ||
| reject(err?.message ?? err); | ||
| }); | ||
| }); | ||
|
Comment on lines
+330
to
+334
|
||
|
|
||
| return Promise.race([joinPromise, rejectPromise, timeoutPromise]); | ||
| } | ||
|
|
||
| private async joinProjectByV2(): Promise<ProjectEntity> { | ||
| const timeoutPromise: Promise<ProjectEntity> = new Promise((_, reject) => { | ||
| setTimeout(() => { | ||
| reject('timeout'); | ||
| }, 5000); | ||
| }); | ||
| return Promise.race([this.record!, timeoutPromise]); | ||
| } | ||
|
|
||
| private shouldFallbackToV2(err: unknown): boolean { | ||
| const message = String(err ?? '').toLowerCase(); | ||
| return message.includes('missing/bad ?projectid') || message.includes('bad projectid') || message.includes('missing projectid'); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When falling back between v1/v2 you call
this.init()to create a new Socket.IO connection, but the existingthis.socketis never disconnected first. This can leave multiple live connections and duplicated event handlers (especially if joinProject retries), leading to duplicated events and resource leaks. Consider disconnecting/cleaning up the current socket (and its listeners) before re-initializing, e.g.,this.socket?.disconnect()plus removing listeners, or refactorinit()to safely re-create the socket.