UNPKG

@datalayer/core

Version:

[![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)

164 lines (163 loc) 5.94 kB
/* * Copyright (c) 2023-2025 Datalayer, Inc. * Distributed under the terms of the Modified BSD License. */ import { WebsocketProvider } from 'y-websocket'; import { URLExt } from '@jupyterlab/coreutils'; import { Signal } from '@lumino/signaling'; // Import CollaborationStatus enum separately since it's exported var CollaborationStatus; (function (CollaborationStatus) { CollaborationStatus["Disconnected"] = "disconnected"; CollaborationStatus["Connecting"] = "connecting"; CollaborationStatus["Connected"] = "connected"; CollaborationStatus["Error"] = "error"; })(CollaborationStatus || (CollaborationStatus = {})); import { requestDatalayerCollaborationSessionId } from './DatalayerCollaboration'; /** * Datalayer collaboration provider * * This provider connects to Datalayer's collaboration service using WebSockets. */ export class DatalayerCollaborationProvider { type = 'datalayer'; _status = CollaborationStatus.Disconnected; _provider = null; _sharedModel = null; _statusChanged = new Signal(this); _errorOccurred = new Signal(this); _syncStateChanged = new Signal(this); _isDisposed = false; _config; _onSync = null; _onConnectionClose = null; constructor(config) { this._config = config; } get status() { return this._status; } get isConnected() { return this._status === CollaborationStatus.Connected; } get isDisposed() { return this._isDisposed; } get events() { return { statusChanged: this._statusChanged, errorOccurred: this._errorOccurred, syncStateChanged: this._syncStateChanged, }; } setStatus(status) { if (this._status !== status) { this._status = status; this._statusChanged.emit(status); } } async connect(sharedModel, documentId, options) { if (this.isConnected) { console.warn('Already connected to Datalayer collaboration service'); return; } this.setStatus(CollaborationStatus.Connecting); try { // Use ONLY explicitly provided configuration // NO fallback to coreStore - if config is wrong, we want to fail fast // This is critical for VS Code extension where config must come from settings const runUrl = this._config.runUrl; const token = this._config.token; if (!runUrl) { throw new Error('Datalayer runUrl is not configured - must be explicitly provided in DatalayerCollaborationProvider config'); } if (!token) { throw new Error('Datalayer token is not configured - must be explicitly provided in DatalayerCollaborationProvider config'); } const { ydoc, awareness } = sharedModel; // Build URLs. const documentURL = URLExt.join(runUrl, '/api/spacer/v1/documents'); const wsUrl = URLExt.join(runUrl, '/api/spacer/v1/documents/ws').replace(/^http/, 'ws'); // Request collaboration session from Datalayer const sessionId = await requestDatalayerCollaborationSessionId({ url: URLExt.join(documentURL, documentId), token, fetchFn: this._config.fetchFn, }); // Create WebSocket provider this._provider = new WebsocketProvider(wsUrl, documentId, ydoc, { disableBc: true, params: { sessionId, token, }, awareness, ...options, }); this._sharedModel = sharedModel; // Set up event handlers this._onSync = (isSynced) => { this.handleSync(isSynced); }; this._onConnectionClose = (event) => { if (event) { this.handleConnectionClose(event); } }; this._provider.on('sync', this._onSync); this._provider.on('connection-close', this._onConnectionClose); console.log('Connected to Datalayer collaboration service'); } catch (error) { this.setStatus(CollaborationStatus.Error); this._errorOccurred.emit(error); throw error; } } disconnect() { if (this._provider) { if (this._onSync) { this._provider.off('sync', this._onSync); } if (this._onConnectionClose) { this._provider.off('connection-close', this._onConnectionClose); } this._provider.disconnect(); this._provider = null; } this._sharedModel = null; this.setStatus(CollaborationStatus.Disconnected); } getProvider() { return this._provider; } getSharedModel() { return this._sharedModel; } handleConnectionClose(event) { console.warn('Collaboration connection closed:', event); this.setStatus(CollaborationStatus.Disconnected); // Handle session expiration (code 4002) if (event.code === 4002) { console.warn('Datalayer collaboration session expired'); // Attempt to reconnect could be implemented here } } handleSync(isSynced) { this._syncStateChanged.emit(isSynced); if (isSynced) { this.setStatus(CollaborationStatus.Connected); } } dispose() { if (this._isDisposed) { return; } this.disconnect(); // Signals don't need explicit disposal in Lumino // They are cleaned up when the object is garbage collected this._isDisposed = true; } } // Export the provider for direct instantiation export default DatalayerCollaborationProvider;