This commit is contained in:
chelsealong 2026-08-14 23:07:37 +08:00 committed by GitHub
commit 302b503800
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 7 deletions

View File

@ -49,11 +49,10 @@ describe('CollaborationManager CRDT runtime loading', () => {
expect(manager.isConnected()).toBe(false)
})
it('does not create connection state when the runtime fails to load and allows a retry', async () => {
it('falls back to local editing instead of leaving the UI stuck when the runtime fails to load', async () => {
const { CollaborationManager, webSocketClient } = await loadCollaborationModules()
const manager = new CollaborationManager()
const runtimeError = new Error('runtime-load-failed')
const retryError = new Error('runtime-retry-failed')
const loadRuntimeSpy = vi
.spyOn(
manager as unknown as {
@ -62,19 +61,44 @@ describe('CollaborationManager CRDT runtime loading', () => {
'loadCrdtRuntime',
)
.mockRejectedValueOnce(runtimeError)
.mockRejectedValueOnce(retryError)
const connectSpy = vi.spyOn(webSocketClient, 'connect')
await expect(manager.connect('app-runtime-failure')).rejects.toBe(runtimeError)
const connectionId = await manager.connect('app-runtime-failure')
expect(loadRuntimeSpy).toHaveBeenCalledTimes(1)
expect(connectSpy).not.toHaveBeenCalled()
expect(manager.isConnected()).toBe(false)
expect(manager.canApplyLocalGraphMutation()).toBe(true)
expect(manager.canPersistLocalGraph()).toBe(true)
await expect(manager.connect('app-runtime-failure')).rejects.toBe(retryError)
manager.disconnect(connectionId)
})
expect(loadRuntimeSpy).toHaveBeenCalledTimes(2)
it('keeps editing blocked when the runtime fails to load on a configured socket URL', async () => {
const { CollaborationManager } = await loadCollaborationModules()
const websocketManager = await import('../websocket-manager')
vi.spyOn(websocketManager, 'isDefaultSocketUrl').mockReturnValue(false)
const manager = new CollaborationManager()
const runtimeError = new Error('runtime-load-failed')
const loadRuntimeSpy = vi
.spyOn(
manager as unknown as {
loadCrdtRuntime: () => Promise<(typeof import('../crdt-runtime'))['crdtRuntime']>
},
'loadCrdtRuntime',
)
.mockRejectedValueOnce(runtimeError)
const connectSpy = vi.spyOn(websocketManager.webSocketClient, 'connect')
const connectionId = await manager.connect('app-runtime-failure-configured-socket')
expect(loadRuntimeSpy).toHaveBeenCalledTimes(1)
expect(connectSpy).not.toHaveBeenCalled()
expect(manager.isConnected()).toBe(false)
expect(manager.canApplyLocalGraphMutation()).toBe(false)
expect(manager.canPersistLocalGraph()).toBe(false)
manager.disconnect(connectionId)
})
it('initializes one session for concurrent consumers of the same app', async () => {

View File

@ -664,7 +664,24 @@ export class CollaborationManager {
}
const connectGeneration = this.connectGeneration
if (!this.crdtRuntime) await this.ensureCrdtRuntime()
if (!this.crdtRuntime) {
try {
await this.ensureCrdtRuntime()
} catch (error) {
console.error('Failed to load collaboration runtime, falling back to local editing:', error)
if (connectGeneration === this.connectGeneration && this.targetAppId === appId) {
this.currentAppId = appId
// Mirrors activateLocalDraftFallback(): only degrade to local-only editing on the
// default self-hosted socket URL. A configured (Cloud/Enterprise) socket implies a real
// collaboration server may have other users editing live, so editing stays blocked here.
if (isDefaultSocketUrl() && !this.hasEstablishedConnection)
this.localDraftFallbackActive = true
this.activeConnections.add(connectionId)
this.emitGraphReadyState()
}
return connectionId
}
}
if (connectGeneration !== this.connectGeneration || this.targetAppId !== appId)
return connectionId