UNPKG

@textea/y-socket.io

Version:
279 lines (271 loc) 10.2 kB
/// <reference types="./provider.d.ts" /> (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('socket.io-client'), require('uuid'), require('y-protocols/awareness'), require('yjs'), require('zustand'), require('zustand/middleware')) : typeof define === 'function' && define.amd ? define(['exports', 'socket.io-client', 'uuid', 'y-protocols/awareness', 'yjs', 'zustand', 'zustand/middleware'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.YSocketIO = {}, global.socket["io-client"], global.uuid, global["y-protocols/awareness"], global.yjs, global.zustand, global["zustand/middleware"])); })(this, (function (exports, socket_ioClient, uuid, awareness, Y, zustand, middleware) { 'use strict'; function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var Y__namespace = /*#__PURE__*/_interopNamespace(Y); const getClients = (awareness)=>[ ...awareness.getStates().keys() ]; const getOtherClients = (awareness)=>{ const clients = getClients(awareness); return clients.filter((clientId)=>clientId !== awareness.clientID); }; const INITIAL_SOCKET_STATE = { connecting: false, connected: false, synced: false, error: null }; /** * @internal */ const INITIAL_STATE = { ...INITIAL_SOCKET_STATE, data: null }; const createSocketIOProvider = (serverUrl, roomName, doc, { awareness: awareness$1 =new awareness.Awareness(doc) , autoConnect =true , autoConnectBroadcastChannel =true } = {})=>{ const syncingDocUpdates = new Set(); const store = zustand.createStore()(middleware.subscribeWithSelector(()=>({ ...INITIAL_STATE, connecting: autoConnect }))); const queryParameters = { roomName, clientId: String(awareness$1.clientID) }; const socket = socket_ioClient.io(serverUrl, { query: queryParameters, autoConnect }); socket.on('connect_error', (err)=>{ store.setState({ connecting: false, error: err.message }); }); socket.on('connect', ()=>{ store.setState({ connecting: false, connected: true, error: null }); const docDiff = Y__namespace.encodeStateVector(doc); socket.emit('doc:diff', docDiff); socket.once('doc:update', ()=>{ if (!syncingDocUpdates.size) { store.setState({ synced: true }); } }); const awarenessUpdate = awareness.encodeAwarenessUpdate(awareness$1, [ awareness$1.clientID ]); socket.emit('awareness:update', awarenessUpdate); }); socket.on('data:update', (data)=>{ store.setState({ data }); }); socket.on('doc:diff', (diff)=>{ const updateV2 = Y__namespace.encodeStateAsUpdateV2(doc, new Uint8Array(diff)); socket.emit('doc:update', updateV2); }); socket.on('doc:update', (updateV2)=>{ Y__namespace.applyUpdateV2(doc, new Uint8Array(updateV2), socket); }); socket.on('awareness:update', (update)=>{ awareness.applyAwarenessUpdate(awareness$1, new Uint8Array(update), socket); }); socket.on('disconnect', (_reason, description)=>{ const err = description instanceof Error ? description : null; syncingDocUpdates.clear(); store.setState({ ...INITIAL_STATE, error: err?.message }); const otherClients = getOtherClients(awareness$1); awareness.removeAwarenessStates(awareness$1, otherClients, socket); }); let broadcastChannel; const broadcastChannelName = new URL(roomName, serverUrl).toString(); const handleBroadcastChannelMessage = (event)=>{ const [eventName] = event.data; switch(eventName){ case 'doc:diff': { const [, diff, clientId] = event.data; const updateV2 = Y__namespace.encodeStateAsUpdateV2(doc, diff); broadcastChannel.postMessage([ 'doc:update', updateV2, clientId ]); break; } case 'doc:update': { const [, updateV21, clientId1] = event.data; if (!clientId1 || clientId1 === awareness$1.clientID) { Y__namespace.applyUpdateV2(doc, updateV21, socket); } break; } case 'awareness:query': { const [, clientId2] = event.data; const clients = getClients(awareness$1); const update = awareness.encodeAwarenessUpdate(awareness$1, clients); broadcastChannel.postMessage([ 'awareness:update', update, clientId2 ]); break; } case 'awareness:update': { const [, update1, clientId3] = event.data; if (!clientId3 || clientId3 === awareness$1.clientID) { awareness.applyAwarenessUpdate(awareness$1, update1, socket); } break; } } }; const connectBroadcastChannel = ()=>{ if (broadcastChannel) { return; } broadcastChannel = Object.assign(new BroadcastChannel(broadcastChannelName), { onmessage: handleBroadcastChannelMessage }); const docDiff = Y__namespace.encodeStateVector(doc); broadcastChannel.postMessage([ 'doc:diff', docDiff, awareness$1.clientID ]); const docUpdateV2 = Y__namespace.encodeStateAsUpdateV2(doc); broadcastChannel.postMessage([ 'doc:update', docUpdateV2 ]); broadcastChannel.postMessage([ 'awareness:query', awareness$1.clientID ]); const awarenessUpdate = awareness.encodeAwarenessUpdate(awareness$1, [ awareness$1.clientID ]); broadcastChannel.postMessage([ 'awareness:update', awarenessUpdate ]); }; const disconnectBroadcastChannel = ()=>{ if (broadcastChannel) { broadcastChannel.close(); broadcastChannel = undefined; } }; if (autoConnectBroadcastChannel) { connectBroadcastChannel(); } const shouldSyncUpdate = ()=>socket.connected || broadcastChannel; const handleDocUpdate = (updateV1, origin)=>{ if (origin === socket || !shouldSyncUpdate()) { return; } const updateV2 = Y__namespace.convertUpdateFormatV1ToV2(updateV1); if (socket.connected) { const updateId = uuid.v4(); syncingDocUpdates.add(updateId); store.setState({ synced: false }); socket.emit('doc:update', updateV2, ()=>{ syncingDocUpdates.delete(updateId); if (!syncingDocUpdates.size) { store.setState({ synced: true }); } }); } broadcastChannel?.postMessage([ 'doc:update', updateV2 ]); }; doc.on('update', handleDocUpdate); const handleAwarenessUpdate = (changes, origin)=>{ if (origin === socket || !shouldSyncUpdate()) { return; } const changedClients = Object.values(changes).reduce((res, cur)=>[ ...res, ...cur ]); const update = awareness.encodeAwarenessUpdate(awareness$1, changedClients); socket.volatile.emit('awareness:update', update); broadcastChannel?.postMessage([ 'awareness:update', update ]); }; awareness$1.on('update', handleAwarenessUpdate); return { getState: store.getState, connect: ()=>{ const { connecting , connected } = store.getState(); if (!connecting && !connected) { store.setState({ connecting: true, error: null }); socket.connect(); } }, closeRoom: ()=>{ socket.volatile.emit('room:close'); }, disconnect: ()=>{ socket.disconnect(); }, connectBroadcastChannel, disconnectBroadcastChannel, subscribe: store.subscribe, destroy: ()=>{ store.destroy(); socket.disconnect(); broadcastChannel?.close(); doc.off('update', handleDocUpdate); awareness$1.off('update', handleAwarenessUpdate); } }; }; exports.INITIAL_STATE = INITIAL_STATE; exports.createSocketIOProvider = createSocketIOProvider; Object.defineProperty(exports, '__esModule', { value: true }); })); //# sourceMappingURL=provider.js.map