@hcikn/colibri 1.3.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +229 -87
- package/dist/index.cjs +1514 -972
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -22
- package/dist/index.d.ts +235 -22
- package/dist/index.js +1480 -884
- package/dist/index.js.map +1 -1
- package/package.json +105 -43
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Colibri.ts","../src/ColibriError.ts","../src/Broadcasting.ts","../src/RemoteLogger.ts","../src/ModelSynchronization.ts","../src/SyncModel.ts","../src/Synced.ts"],"names":["Subject"],"mappings":"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","sourcesContent":["import { Socket, connect } from 'socket.io-client';\r\nimport ColibriError from './ColibriError';\r\nimport { Subject } from 'rxjs';\r\n\r\nexport interface Message {\r\n channel: string;\r\n command: string;\r\n payload: unknown;\r\n}\r\n\r\nexport class Colibri {\r\n private static instance: Colibri | null = null;\r\n\r\n private readonly socket: Socket;\r\n private readonly messageSubject = new Subject<Message>();\r\n public readonly messages = this.messageSubject.asObservable();\r\n public readonly uri: string;\r\n public readonly uriRestApi: string;\r\n\r\n public constructor(\r\n public readonly app: string,\r\n public readonly server: string = window?.location?.hostname ?? '',\r\n public readonly port: number = 9011\r\n ) {\r\n if (server.trim().length <= 0) {\r\n throw new ColibriError('Server Address missing or empty!');\r\n }\r\n\r\n if (port < 1 || port > 65535) {\r\n throw new ColibriError('Port out of allowed range (0 - 65535)');\r\n }\r\n\r\n this.uri = `${server}:${port}`;\r\n if (!new RegExp('wss?://', 'i').test(this.uri)) {\r\n this.uri = `ws://${this.uri}`;\r\n }\r\n this.uriRestApi = `${this.uri}/api/store/${app}/`;\r\n\r\n // there is already an instance running\r\n // do we actually need a second ?!\r\n if (Colibri.instance)\r\n throw new ColibriError('A Colibri instance already exists!');\r\n else Colibri.instance = this;\r\n\r\n this.socket = connect(this.uri, {\r\n query: { app, version: '1' },\r\n transports: ['websocket']\r\n });\r\n this.socket.on('connect', this.onSocketConnect.bind(this));\r\n this.socket.onAny(this.onSocketAny.bind(this));\r\n\r\n // latency statistics\r\n this.registerChannel('colibri', (msg) => {\r\n if (msg.command === 'latency') {\r\n SendMessage('colibri', 'latency', msg.payload);\r\n }\r\n });\r\n }\r\n\r\n private onSocketConnect() {\r\n console.debug(`Connected to colibri server on ${this.server}`);\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private onSocketAny(channel: string, msg: any) {\r\n this.messageSubject.next({\r\n channel,\r\n command: msg.command,\r\n payload: msg.payload,\r\n });\r\n }\r\n\r\n /**\r\n * Returns the existing Colibri instance or null if there's none yet\r\n * @param warnIfNotInitialized true => print Log message if Colibri has not been initialized yet\r\n * @returns\r\n */\r\n public static getInstance(\r\n warnIfNotInitialized: boolean = true\r\n ): Colibri | null {\r\n if (warnIfNotInitialized && !Colibri.instance) {\r\n console.warn('Colibri not initialized yet! (Instance is null)');\r\n }\r\n return Colibri.instance;\r\n }\r\n\r\n /**\r\n * Sends a single message in the given `channel`.\r\n * @param channel message channel\r\n * @param command message command\r\n * @param payload message payload\r\n */\r\n public sendMessage(\r\n channel: string,\r\n command: string,\r\n payload: unknown = {}\r\n ) {\r\n this.socket.emit(channel, {\r\n command,\r\n payload,\r\n });\r\n }\r\n\r\n //#region Socket Events\r\n /**\r\n * Adds a `handler` function listening for messages in `channel`.\r\n * @param channel message channel\r\n * @param handler handler to be executed when a message is received\r\n */\r\n public registerChannel(\r\n channel: string,\r\n handler: (payload: Message) => void\r\n ) {\r\n this.socket.on(channel, handler);\r\n }\r\n\r\n /**\r\n * Removes the `handler` function listening for messages in `channel`.\r\n * @param channel message channel\r\n * @param handler handler to be executed when a message is received\r\n */\r\n public unregisterChannel(\r\n channel: string,\r\n handler: (payload: Message) => void\r\n ) {\r\n this.socket.off(channel, handler);\r\n }\r\n\r\n /**\r\n * Adds a one-time `handler` function listening for the next message in `channel`.\r\n * @param channel message channel\r\n * @param handler handler to be executed when a message is received\r\n */\r\n public registerOnce(channel: string, handler: (payload: Message) => void) {\r\n this.socket.once(channel, handler);\r\n }\r\n //#endregion\r\n\r\n //#region Rest API\r\n /**\r\n * Returns the REST API Endpoint for a given `key` or null of the key is empty.\r\n * @param key REST API storage key\r\n * @returns\r\n */\r\n public getRestUri(key: string): string | null {\r\n key = key.trim();\r\n while (key.startsWith('/')) key = key.substring(1);\r\n return key.length === 0 ? null : this.uriRestApi + key;\r\n }\r\n /**\r\n * Queries an object from the REST API identified by `key`.\r\n * @param key REST API storage key\r\n * @returns JSON object with data or null if object does not exist or any other error occurrs\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n public async getRestObject(key: string): Promise<any | null> {\r\n const uri = this.getRestUri(key);\r\n if (!uri) return null;\r\n\r\n const response = await fetch(uri, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n });\r\n\r\n if (response.status >= 400) return null;\r\n else return response.json();\r\n }\r\n\r\n /**\r\n * Sets or updatees an object in the REST API identified by `key`.\r\n * @param key REST API storage key\r\n * @param data JSON data to write\r\n * @returns true if data was written to REST API\r\n */\r\n public async setRestObject(key: string, data: unknown): Promise<boolean> {\r\n const uri = this.getRestUri(key);\r\n if (!uri) return false;\r\n\r\n const response = await fetch(uri, {\r\n method: 'PUT',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify(data),\r\n });\r\n\r\n return response.status >= 200 && response.status < 300;\r\n }\r\n //#endregion\r\n}\r\n\r\n/////////////////////////////////////\r\n/// Wrapper Functions\r\n// (also for backward compatibility)\r\n/////////////////////////////////////\r\n\r\n/**\r\n * @see {@link Colibri.sendMessage `Colibri.sendMessage()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const SendMessage = (\r\n channel: string,\r\n command: string,\r\n payload: unknown = {}\r\n) => Colibri.getInstance()?.sendMessage(channel, command, payload);\r\n\r\n/**\r\n * @see {@link Colibri.registerChannel `Colibri.registerChannel()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const RegisterChannel = (\r\n channel: string,\r\n handler: (payload: Message) => void\r\n) => Colibri.getInstance()?.registerChannel(channel, handler);\r\n\r\n/**\r\n * @see {@link Colibri.unregisterChannel `Colibri.unregisterChannel()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const UnregisterChannel = (\r\n channel: string,\r\n handler: (payload: Message) => void\r\n) => Colibri.getInstance()?.unregisterChannel(channel, handler);\r\n\r\n/**\r\n * @see {@link Colibri.registerOnce `Colibri.registerOnce()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const RegisterOnce = (\r\n channel: string,\r\n handler: (payload: Message) => void\r\n) => Colibri.getInstance()?.registerOnce(channel, handler);\r\n\r\n/**\r\n * @see {@link Colibri.getRestObject `Colibri.getRestObject()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const GetRestApi = (key: string) =>\r\n Colibri.getInstance()?.getRestObject(key);\r\n\r\n/**\r\n * @see {@link Colibri.setRestObject `Colibri.setRestObject()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const PutRestApi = (key: string, data: unknown) =>\r\n Colibri.getInstance()?.setRestObject(key, data);\r\n","import { CustomError } from 'ts-custom-error';\r\n\r\nexport default class ColibriError extends CustomError {\r\n constructor(message?: string) {\r\n super(message);\r\n }\r\n}\r\n","import { RegisterChannel, SendMessage } from './Colibri';\r\n\r\nconst sendBool = (channel: string, val: boolean) => {\r\n SendMessage(channel, 'broadcast::bool', val);\r\n};\r\n\r\nconst sendBoolArray = (channel: string, val: boolean[]) => {\r\n SendMessage(channel, 'broadcast::bool[]', val);\r\n};\r\n\r\nconst sendNumber = (channel: string, val: number) => {\r\n SendMessage(channel, 'broadcast::float', val);\r\n};\r\n\r\nconst sendNumberArray = (channel: string, val: number[]) => {\r\n SendMessage(channel, 'broadcast::float[]', val);\r\n};\r\n\r\nconst sendString = (channel: string, val: string) => {\r\n SendMessage(channel, 'broadcast::string', val);\r\n};\r\n\r\nconst sendStringArray = (channel: string, val: string[]) => {\r\n SendMessage(channel, 'broadcast::string[]', val);\r\n};\r\n\r\nconst sendVector3 = (channel: string, val: [number, number, number]) => {\r\n SendMessage(channel, 'broadcast::vector3', val);\r\n};\r\n\r\nconst sendVector3Array = (channel: string, val: [number, number, number][]) => {\r\n SendMessage(channel, 'broadcast::vector3[]', val);\r\n};\r\n\r\nconst sendQuaternion = (\r\n channel: string,\r\n val: [number, number, number, number]\r\n) => {\r\n SendMessage(channel, 'broadcast::quaternion', val);\r\n};\r\n\r\nconst sendQuaternionArray = (\r\n channel: string,\r\n val: [number, number, number, number][]\r\n) => {\r\n SendMessage(channel, 'broadcast::quaternion[]', val);\r\n};\r\n\r\nconst sendColor = (channel: string, val: [number, number, number, number]) => {\r\n SendMessage(channel, 'broadcast::color', val);\r\n};\r\n\r\nconst sendColorArray = (\r\n channel: string,\r\n val: [number, number, number, number][]\r\n) => {\r\n SendMessage(channel, 'broadcast::color[]', val);\r\n};\r\n\r\nconst sendJson = (channel: string, val: { [key: string]: unknown }) => {\r\n SendMessage(channel, 'broadcast::json', val);\r\n};\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\ntype genericCallback = (val: any) => void;\r\nconst listeners: {\r\n [channel: string]: { [command: string]: genericCallback[] };\r\n} = {};\r\n\r\nconst registerListener = <T>(\r\n channel: string,\r\n type: string,\r\n callback: (val: T) => void\r\n) => {\r\n if (listeners[channel] === undefined) {\r\n listeners[channel] = {};\r\n RegisterChannel(channel, (msg) => {\r\n if (msg.command in listeners[channel]) {\r\n listeners[channel][msg.command].forEach((cb) =>\r\n cb(msg.payload as T)\r\n );\r\n }\r\n });\r\n }\r\n\r\n if (listeners[channel][type] === undefined) {\r\n listeners[channel][type] = [];\r\n }\r\n\r\n listeners[channel][type].push(callback);\r\n};\r\n\r\nconst receiveBool = (channel: string, callback: (val: boolean) => void) => {\r\n registerListener<boolean>(channel, 'broadcast::bool', callback);\r\n};\r\n\r\nconst receiveBoolArray = (\r\n channel: string,\r\n callback: (val: boolean[]) => void\r\n) => {\r\n registerListener<boolean[]>(channel, 'broadcast::bool[]', callback);\r\n};\r\n\r\nconst receiveNumber = (channel: string, callback: (val: number) => void) => {\r\n registerListener<number>(channel, 'broadcast::float', callback);\r\n};\r\n\r\nconst receiveNumberArray = (\r\n channel: string,\r\n callback: (val: number[]) => void\r\n) => {\r\n registerListener<number[]>(channel, 'broadcast::float[]', callback);\r\n};\r\n\r\nconst receiveString = (channel: string, callback: (val: string) => void) => {\r\n registerListener<string>(channel, 'broadcast::string', callback);\r\n};\r\n\r\nconst receiveStringArray = (\r\n channel: string,\r\n callback: (val: string[]) => void\r\n) => {\r\n registerListener<string[]>(channel, 'broadcast::string[]', callback);\r\n};\r\n\r\nconst receiveVector3 = (\r\n channel: string,\r\n callback: (val: [number, number, number]) => void\r\n) => {\r\n registerListener<[number, number, number]>(\r\n channel,\r\n 'broadcast::vector3',\r\n callback\r\n );\r\n};\r\n\r\nconst receiveVector3Array = (\r\n channel: string,\r\n callback: (val: [number, number, number][]) => void\r\n) => {\r\n registerListener<[number, number, number][]>(\r\n channel,\r\n 'broadcast::vector3[]',\r\n callback\r\n );\r\n};\r\n\r\nconst receiveQuaternion = (\r\n channel: string,\r\n callback: (val: [number, number, number, number]) => void\r\n) => {\r\n registerListener<[number, number, number, number]>(\r\n channel,\r\n 'broadcast::quaternion',\r\n callback\r\n );\r\n};\r\n\r\nconst receiveQuaternionArray = (\r\n channel: string,\r\n callback: (val: [number, number, number, number][]) => void\r\n) => {\r\n registerListener<[number, number, number, number][]>(\r\n channel,\r\n 'broadcast::quaternion[]',\r\n callback\r\n );\r\n};\r\n\r\nconst receiveColor = (channel: string, callback: (val: string) => void) => {\r\n registerListener<string>(channel, 'broadcast::color', callback);\r\n};\r\n\r\nconst receiveColorArray = (\r\n channel: string,\r\n callback: (val: string[]) => void\r\n) => {\r\n registerListener<string[]>(channel, 'broadcast::color[]', callback);\r\n};\r\n\r\nconst receiveJson = (\r\n channel: string,\r\n callback: (val: { [key: string]: unknown }) => void\r\n) => {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n registerListener<any>(channel, 'broadcast::json', callback);\r\n};\r\n\r\nconst unregister = (channel: string, callback: genericCallback) => {\r\n for (const command in listeners[channel]) {\r\n const index = listeners[channel][command].indexOf(callback);\r\n if (index >= 0) {\r\n listeners[channel][command].splice(index, 1);\r\n }\r\n }\r\n\r\n // TODO: we should ideally unsubscribe from the channel if there are no more listeners\r\n};\r\n\r\nexport const Sync = {\r\n sendBool,\r\n sendBoolArray,\r\n sendNumber,\r\n sendNumberArray,\r\n sendString,\r\n sendStringArray,\r\n sendVector3,\r\n sendVector3Array,\r\n sendQuaternion,\r\n sendQuaternionArray,\r\n sendColor,\r\n sendColorArray,\r\n sendJson,\r\n\r\n receiveBool,\r\n receiveBoolArray,\r\n receiveNumber,\r\n receiveNumberArray,\r\n receiveString,\r\n receiveStringArray,\r\n receiveVector3,\r\n receiveVector3Array,\r\n receiveQuaternion,\r\n receiveQuaternionArray,\r\n receiveColor,\r\n receiveColorArray,\r\n receiveJson,\r\n\r\n unregister,\r\n\r\n // for better compatibility with Unity Colibri\r\n sendFloat: sendNumber,\r\n sendInt: sendNumber,\r\n sendFloatArray: sendNumberArray,\r\n sendIntArray: sendNumberArray,\r\n};\r\n","import { SendMessage } from './Colibri';\r\n\r\nexport class RemoteLogger {\r\n private readonly consoleDebug = console.debug;\r\n private readonly consoleLog = console.log;\r\n private readonly consoleInfo = console.info;\r\n private readonly consoleWarn = console.warn;\r\n private readonly consoleError = console.error;\r\n\r\n /**\r\n * @deprecated use new class constructor directly\r\n */\r\n public static init(enabled: boolean = true) {\r\n return new RemoteLogger(enabled);\r\n }\r\n\r\n public constructor(private enabled: boolean = true) {\r\n // intercept calls from console\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n console.debug = (...args: any[]) => {\r\n this.consoleDebug(...args);\r\n this.sendMessage('debug', args);\r\n };\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n console.log = (...args: any[]) => {\r\n this.consoleLog(...args);\r\n this.sendMessage('info', args);\r\n };\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n console.info = (...args: any[]) => {\r\n this.consoleInfo(...args);\r\n this.sendMessage('info', args);\r\n };\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n console.warn = (...args: any[]) => {\r\n this.consoleWarn(...args);\r\n this.sendMessage('warn', args);\r\n };\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n console.error = (...args: any[]) => {\r\n this.consoleError(...args);\r\n this.sendMessage('error', args);\r\n };\r\n }\r\n\r\n private sendMessage(\r\n level: 'error' | 'warn' | 'info' | 'debug',\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n args: any[]\r\n ) {\r\n if (!this.enabled) return;\r\n SendMessage('log', level, [...args].map(stringify).join().trim());\r\n }\r\n\r\n public enable() {\r\n this.enabled = true;\r\n }\r\n\r\n public disable() {\r\n this.enabled = false;\r\n }\r\n}\r\n\r\nconst stringify = (obj: unknown): string => {\r\n if (obj instanceof Error) {\r\n return obj.message + '\\n' + obj.stack;\r\n }\r\n\r\n if (typeof obj === 'string') return obj as string;\r\n\r\n const cache: unknown[] = [];\r\n const str = JSON.stringify(\r\n obj,\r\n (_, value: unknown) => {\r\n if (typeof value === 'object' && value !== null) {\r\n if (cache.indexOf(value) !== -1) {\r\n // Circular reference found, discard key\r\n return;\r\n }\r\n // Store value in our collection\r\n cache.push(value);\r\n }\r\n return value;\r\n },\r\n 2\r\n );\r\n return str;\r\n};\r\n","import { BehaviorSubject, Observable } from 'rxjs';\r\nimport { Message, RegisterChannel, SendMessage } from './Colibri';\r\nimport { SyncModel } from './SyncModel';\r\n\r\ninterface ModelSyncMsg<T extends SyncModel<T>> extends Message {\r\n command: 'model::update' | 'model::delete';\r\n payload: Partial<T> & { id: string };\r\n}\r\n\r\ninterface ModelSyncRegistration<T> {\r\n name?: string;\r\n type: { new (id: string): T };\r\n}\r\n\r\ntype ModelSync<T> = [Observable<T[]>, (model: T) => void];\r\n\r\nexport const RegisterModelSync = <T extends SyncModel<T>>(\r\n registration: ModelSyncRegistration<T>\r\n): ModelSync<T> => {\r\n const name = registration.name || registration.type.name.toLowerCase();\r\n\r\n // initial data fetch\r\n SendMessage(name, 'model::request');\r\n\r\n // Register for updates\r\n console.log(`Registering model '${name}'`);\r\n RegisterChannel(`${name}`, (payload: Message) => {\r\n const msg = payload as ModelSyncMsg<T>;\r\n if (msg.command === 'model::update') {\r\n onUpdate(msg.payload);\r\n } else if (msg.command === 'model::delete') {\r\n onDelete(msg.payload.id);\r\n } else {\r\n console.error(`Unknown model command: ${msg.command}`);\r\n }\r\n });\r\n\r\n // Handle updates\r\n const models = new BehaviorSubject<T[]>([]);\r\n\r\n const onUpdate = (modelData: Partial<T>) => {\r\n const model = models.value.find((m) => m.id === modelData.id);\r\n if (model) {\r\n // Update existing model\r\n model.update(modelData);\r\n models.next([...models.value]);\r\n } else if (modelData.id) {\r\n const newModel = new registration.type(modelData.id);\r\n\r\n newModel.modelChanges$.subscribe((changes) => {\r\n // If we did the changes, we'll ignore it\r\n if (!newModel.ignoreNextChange) {\r\n SendMessage(\r\n `${name}`,\r\n 'model::update',\r\n newModel.toJson(changes)\r\n );\r\n }\r\n newModel.ignoreNextChange = false;\r\n models.next([...models.value]);\r\n });\r\n\r\n newModel.update(modelData);\r\n models.next([...models.value, newModel]);\r\n }\r\n };\r\n\r\n const onDelete = (id: string) => {\r\n const model = models.value.find((m) => m.id === id);\r\n model?.delete();\r\n models.next(models.value.filter((m) => m.id !== id));\r\n };\r\n\r\n const registerModel = (model: T) => {\r\n model.modelChanges$.subscribe((changes) => {\r\n // If we did the changes, we'll ignore it\r\n if (!model.ignoreNextChange) {\r\n SendMessage(`${name}`, 'model::update', model.toJson(changes));\r\n }\r\n model.ignoreNextChange = false;\r\n });\r\n\r\n // send initial model\r\n SendMessage(`${name}`, 'model::update', model.toJson());\r\n models.next([...models.value, model]);\r\n };\r\n\r\n return [models.asObservable(), registerModel];\r\n};\r\n","import { Subject, bufferTime, filter, map, share } from 'rxjs';\r\n\r\nexport abstract class SyncModel<T> {\r\n public readonly id: string;\r\n\r\n // FIXME: this is set in 'registerSyncedProperty' but the decorator is called after the constructor\r\n // FIXME: -> therefore only available via prototype!\r\n private syncedProperties!: { [key: string]: (string | symbol) };\r\n\r\n // TODO: move to ModelSynchronization or use a much better structure or something like that?\r\n public ignoreNextChange = false;\r\n\r\n public readonly modelChanges = new Subject<string>();\r\n public readonly modelChanges$ = this.modelChanges.pipe(\r\n bufferTime(1),\r\n map(changes => changes.filter(this.uniq)),\r\n filter(changes => changes.length > 0),\r\n share());\r\n\r\n public constructor(id: string) {\r\n this.id = id;\r\n }\r\n\r\n private uniq(value: string, index: number, array: string[]) {\r\n return array.indexOf(value) === index;\r\n }\r\n\r\n protected onModelChanges(prop: string): void {\r\n this.modelChanges.next(prop);\r\n }\r\n\r\n public delete(): void {\r\n this.modelChanges.complete();\r\n }\r\n\r\n public registerSyncedProperty(name: string, prop: string | symbol): void {\r\n // workaround since decorators are called before constructors\r\n if (!this.syncedProperties) {\r\n this.syncedProperties = {};\r\n }\r\n\r\n this.syncedProperties[name] = prop;\r\n }\r\n\r\n public update(updates: Partial<T>): void {\r\n const syncedProps = Object.getPrototypeOf(this).syncedProperties;\r\n\r\n for (const key of Object.keys(updates)) {\r\n if (key !== 'id') {\r\n const localKey = syncedProps[key];\r\n if (localKey) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (this as any)[localKey as keyof SyncModel<T>] = updates[key as keyof T];\r\n this.ignoreNextChange = true;\r\n } else {\r\n console.warn(`Unknown property ${key} in ${this.constructor.name}`);\r\n }\r\n }\r\n }\r\n }\r\n\r\n public toJson(attributes: string[] = []): Partial<T> {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const json: any = { id: this.id };\r\n\r\n // TODO: only changed properties\r\n const syncedProps = Object.getPrototypeOf(this).syncedProperties;\r\n for (const key of Object.keys(syncedProps)) {\r\n const localKey = syncedProps[key] as keyof SyncModel<T>;\r\n if (attributes.length === 0 || attributes.includes(localKey)) {\r\n json[key] = this[localKey];\r\n }\r\n }\r\n\r\n return json;\r\n }\r\n}","import { SyncModel } from './SyncModel';\r\n\r\n/**\r\n * This decorator can be attached to any property or accessor of a SyncModel. \r\n * It will cause the property to be synchronized with the server.\r\n * Refer to https://www.typescriptlang.org/docs/handbook/decorators.html\r\n */\r\nexport const Synced = <T>(syncedName: string = ''): PropertyDecorator => {\r\n\r\n return function (target: unknown, key: string | symbol, descriptor?: PropertyDescriptor): void {\r\n if (!syncedName)\r\n syncedName = key.toString();\r\n syncedName = syncedName.toLowerCase();\r\n\r\n if (!(target instanceof SyncModel)) {\r\n console.error('Synced decorator can only be used on SyncModel properties');\r\n return;\r\n }\r\n\r\n let original = target[key as keyof Object];\r\n // FIXME: this sets the property on the prototype, not the instance!\r\n target.registerSyncedProperty(syncedName, key);\r\n\r\n if (!descriptor?.get) {\r\n /**\r\n * TODO: This (i.e., for properties) doesn't work in react for some reason!\r\n */\r\n Reflect.deleteProperty(target, key);\r\n Reflect.defineProperty(target, key, {\r\n get: () => original,\r\n set: newVal => {\r\n console.log(`Set ${key.toString()} to ${newVal}`);\r\n original = newVal;\r\n },\r\n enumerable: true,\r\n configurable: true\r\n });\r\n } else {\r\n const originalAccessors = {\r\n get: descriptor.get,\r\n set: descriptor.set\r\n };\r\n\r\n descriptor.get = function (): T {\r\n const ret: T = originalAccessors.get.call(this);\r\n return ret;\r\n };\r\n\r\n descriptor.set = function (newval: T) {\r\n const model = this as SyncModel<T>;\r\n model.modelChanges.next(String(key));\r\n\r\n if (originalAccessors.set) {\r\n originalAccessors.set.call(this, newval);\r\n }\r\n };\r\n }\r\n };\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/Colibri.ts","../src/ColibriError.ts","../src/lifecycle.ts","../src/Broadcasting.ts","../src/RemoteLogger.ts","../src/ModelSynchronization.ts","../src/SyncModel.ts","../src/Synced.ts","../src/SyncTransform.ts"],"sourcesContent":["import { Subject } from 'rxjs';\r\nimport { Socket, connect } from 'socket.io-client';\r\nimport { ColibriError, ProtocolMismatchError } from './ColibriError';\r\nimport { colibriCreated, colibriDisconnected, colibriHeardFrom, colibriReconnected } from './lifecycle';\r\n\r\n/**\r\n * The wire protocol version this client speaks, sent in the handshake query. Must match the\r\n * server's `PROTOCOL_VERSION` (`src/server/modules/networking/protocol.ts`); a server refuses a\r\n * client that announces another version.\r\n */\r\nexport const PROTOCOL_VERSION = '2';\r\n\r\n// Accept of every REST request, and Content-Type of setRestObject. colibri-server 2.x refuses a\r\n// value sent as plain application/json. The version in it lets a reverse proxy route by Accept.\r\nconst REST_MEDIA_TYPE = `application/vnd.colibri.v${PROTOCOL_VERSION}+json`;\r\n\r\nconst COLIBRI_CHANNEL = 'colibri';\r\n// The app name of the server's admin UI. Unrelated to the channel above, though spelt the same.\r\nconst ADMIN_APP = 'colibri';\r\nconst PROTOCOL_REJECTED_COMMAND = 'protocol::rejected';\r\nconst PROTOCOL_ACCEPTED_COMMAND = 'protocol::accepted';\r\nconst LATENCY_COMMAND = 'latency';\r\n\r\n// How long to wait for `colibri`/`protocol::accepted` before concluding the server predates the\r\n// version check. A 2.0.0+ server sends it immediately on connect; the window only covers an\r\n// event-loop stall on a loaded server.\r\n//\r\n// Not inferred from the 100ms `latency` broadcast: 1.2.x and 1.3.x servers send it but speak v1.\r\nconst OLD_SERVER_TIMEOUT_MS = 5000;\r\n\r\n// The protocol of every release before 2.0.0, none of which announce themselves.\r\nconst OLD_SERVER_PROTOCOL_VERSION = '1';\r\n\r\ninterface ProtocolRejection {\r\n reason?: string;\r\n serverVersion?: string;\r\n clientVersion?: string;\r\n}\r\n\r\n// The \"dom\" lib declares these as non-optional, but under plain Node (samples, e2e) they are\r\n// absent. Shadowed so the types say so.\r\ndeclare const window: Window | undefined;\r\ndeclare const document: Document | undefined;\r\n\r\n// The default server: the host that served the page. `typeof` rather than `window?.`, since under\r\n// Node `window` is undeclared and reading it throws a ReferenceError.\r\nconst pageHostname = (): string =>\r\n typeof window !== 'undefined' ? ((window.location as Location | undefined)?.hostname ?? '') : '';\r\n\r\n// Every scheme a server address may use, mapped to the socket and REST schemes, which have to\r\n// agree on TLS. http(s) is accepted because a browser's address bar shows it for the same server.\r\nconst SCHEMES: Partial<Record<string, { socket: string; rest: string }>> = {\r\n ws: { socket: 'ws', rest: 'http' },\r\n http: { socket: 'ws', rest: 'http' },\r\n wss: { socket: 'wss', rest: 'https' },\r\n https: { socket: 'wss', rest: 'https' }\r\n};\r\n\r\nconst parseServerAddress = (server: string) => {\r\n const address = server.trim();\r\n const match = /^([a-z][a-z0-9+.-]*):\\/\\/(.*)$/i.exec(address);\r\n const schemes = match ? SCHEMES[match[1].toLowerCase()] : SCHEMES.ws;\r\n if (!schemes) {\r\n throw new ColibriError(\r\n `Unsupported scheme in server address '${server}' - use ws://, wss://, http://, https:// or none.`\r\n );\r\n }\r\n\r\n // Trailing slashes come from copying an address bar; they would put the port after the slash.\r\n const authority = (match ? match[2] : address).replace(/\\/+$/, '');\r\n if (authority.length === 0) {\r\n throw new ColibriError('Server Address missing or empty!');\r\n }\r\n\r\n // Rejected rather than dropped: the URLs are host and port only, so a path would land before\r\n // the port, and dropping it would connect to something other than what was asked for.\r\n if (/[/?#]/.test(authority)) {\r\n throw new ColibriError(\r\n `Server address '${server}' has a path or query after the host - pass only the host and, optionally, the port, e.g. 'http://example.com:9211'.`\r\n );\r\n }\r\n\r\n // Host, then an optional port. An IPv6 host is bracketed to tell its colons from the port's.\r\n const hostAndPort = /^(\\[[^\\]]+\\]|[^:]+)(?::(\\d+))?$/.exec(authority);\r\n if (!hostAndPort) {\r\n throw new ColibriError(\r\n `Server address '${server}' is not a host, or a host and a numeric port - an IPv6 address goes in brackets.`\r\n );\r\n }\r\n\r\n // `at`: an optional group that did not match is undefined, which indexing's type hides.\r\n const portText = hostAndPort.at(2);\r\n return { ...schemes, host: hostAndPort[1], port: portText === undefined ? undefined : Number(portText) };\r\n};\r\n\r\n// colibri-server 2.x's default web port. 1.x's was 9011: they differ so that a client of one\r\n// version does not reach a server of the other by default.\r\nconst DEFAULT_PORT = 9211;\r\n\r\nconst checkPort = (port: number) => {\r\n // isInteger also rejects NaN, which passes the range check below.\r\n if (!Number.isInteger(port)) {\r\n throw new ColibriError(`Port must be a whole number (1 - 65535): ${port}`);\r\n }\r\n if (port < 1 || port > 65535) {\r\n throw new ColibriError(`Port out of allowed range (1 - 65535): ${port}`);\r\n }\r\n};\r\n\r\n// Plain JavaScript callers, query strings and environment variables give the port as a string.\r\n// 1.x accepted '9011', so a digit string still means that port; anything else is left for\r\n// checkPort to refuse.\r\nconst coercePort = (port: unknown): number | undefined =>\r\n typeof port === 'string' && /^\\s*\\d+\\s*$/.test(port) ? Number(port) : (port as number | undefined);\r\n\r\n// The port may come from the address, from the argument, or from both if they agree. Two\r\n// different ports are a mistake, and picking either would hide it.\r\nconst resolvePort = (server: string, fromAddress: number | undefined, given: number | undefined): number => {\r\n if (given !== undefined) checkPort(given);\r\n if (fromAddress === undefined) return given ?? DEFAULT_PORT;\r\n\r\n checkPort(fromAddress);\r\n if (given !== undefined && given !== fromAddress) {\r\n throw new ColibriError(\r\n `Server address '${server}' has port ${fromAddress}, but port ${given} was passed as well - give the port once.`\r\n );\r\n }\r\n return fromAddress;\r\n};\r\n\r\nexport interface Message {\r\n channel: string;\r\n command: string;\r\n payload: unknown;\r\n}\r\n\r\n// Handlers given to RegisterChannel/RegisterOnce before `new Colibri()`; the constructor attaches them.\r\nconst pendingRegistrations: { channel: string; handler: (payload: Message) => void; once: boolean }[] = [];\r\n\r\nexport class Colibri {\r\n private static instance: Colibri | null = null;\r\n\r\n private readonly socket: Socket;\r\n private readonly messageSubject = new Subject<Message>();\r\n public readonly messages = this.messageSubject.asObservable();\r\n private readonly protocolMismatchSubject = new Subject<ProtocolMismatchError>();\r\n /**\r\n * Reports that this client and the server do not speak the same wire protocol. Two kinds,\r\n * told apart by {@link ProtocolMismatchError.fatal `fatal`}, each emitted at most once per\r\n * instance:\r\n *\r\n * - `fatal: true`: the server refused this client. The connection is closed and not retried.\r\n * - `fatal: false`: the server did not announce itself within 5s of connecting, so it is\r\n * suspected to predate colibri-server 2.0.0. The connection stays up.\r\n *\r\n * A suspicion can be followed by a refusal, never the other way round. Nothing is replayed to\r\n * a late subscriber, so subscribe right after construction. Both kinds are also logged.\r\n */\r\n public readonly protocolMismatch = this.protocolMismatchSubject.asObservable();\r\n public readonly uri: string;\r\n public readonly uriRestApi: string;\r\n /** The port connected to: the one in the server address, else the one passed, else 9211. */\r\n public readonly port: number;\r\n\r\n private oldServerTimer: ReturnType<typeof setTimeout> | undefined;\r\n // Once per instance, not per connection, so a reconnect does not repeat the warning.\r\n private hasReportedOldServer = false;\r\n // Once per instance, not once per `protocol::rejected` received.\r\n private hasReportedRefusal = false;\r\n\r\n private hasConnected = false;\r\n\r\n // An outage runs from a failed connection attempt to the next connect.\r\n private hasReportedOutage = false;\r\n\r\n /**\r\n * Connects to a Colibri server. Only one instance may exist.\r\n * @param app the application name; clients only see each other's messages within one app\r\n * @param server the host, optionally after `ws://`, `wss://`, `http://` or `https://` (the\r\n * secure ones mean `wss` for the socket and `https` for REST) and optionally with a port,\r\n * as in `'http://example.com:9211'`; no path. Defaults to the host that served the page,\r\n * and is required outside a browser.\r\n * @param port the server port, if the address has none; 9211 if neither does. When both have\r\n * one, they must agree.\r\n * @throws ColibriError for an address or port this client cannot connect to, or when an\r\n * instance already exists\r\n */\r\n public constructor(\r\n public readonly app: string,\r\n public readonly server: string = pageHostname(),\r\n port?: number\r\n ) {\r\n if (server.trim().length <= 0) {\r\n throw new ColibriError(\r\n typeof window === 'undefined'\r\n ? 'Server Address missing or empty! Outside a browser there is no page to take it from, so pass it as the second argument.'\r\n : 'Server Address missing or empty!'\r\n );\r\n }\r\n\r\n const address = parseServerAddress(server);\r\n this.port = resolvePort(server, address.port, coercePort(port));\r\n this.uri = `${address.socket}://${address.host}:${this.port}`;\r\n // Encoded like the key in getRestUri; the server decodes it back to the handshake's app name.\r\n this.uriRestApi = `${address.rest}://${address.host}:${this.port}/api/store/${encodeURIComponent(app)}/`;\r\n\r\n // there is already an instance running\r\n if (Colibri.instance) throw new ColibriError('A Colibri instance already exists!');\r\n else Colibri.instance = this;\r\n\r\n // The server tells the admin UI from an application by this name alone. Only warned about,\r\n // so an app already named 'colibri' keeps working.\r\n if (app === ADMIN_APP) {\r\n console.warn(\r\n `Colibri: the app name '${ADMIN_APP}' is reserved for the server's admin UI, and the server ` +\r\n `treats this client as one: it sends it the server's log as it is written and every ` +\r\n `client's connects and disconnects, shares its messages with every open admin UI, and ` +\r\n `skips the protocol version check. Give your app a name of its own.`\r\n );\r\n }\r\n\r\n this.socket = connect(this.uri, {\r\n query: { app, version: PROTOCOL_VERSION },\r\n transports: ['websocket']\r\n });\r\n this.socket.on('connect', this.onSocketConnect.bind(this));\r\n this.socket.on('disconnect', this.onSocketDisconnect.bind(this));\r\n this.socket.on('connect_error', this.onSocketConnectError.bind(this));\r\n this.socket.onAny(this.onSocketAny.bind(this));\r\n\r\n // latency statistics\r\n this.registerChannel(COLIBRI_CHANNEL, msg => {\r\n if (msg.command === LATENCY_COMMAND) {\r\n SendMessage(COLIBRI_CHANNEL, LATENCY_COMMAND, msg.payload);\r\n }\r\n });\r\n\r\n // Everything registered before this instance existed, in registration order.\r\n for (const { channel, handler, once } of pendingRegistrations.splice(0)) {\r\n if (once) this.registerOnce(channel, handler);\r\n else this.registerChannel(channel, handler);\r\n }\r\n colibriCreated(this);\r\n }\r\n\r\n private onSocketConnect() {\r\n console.debug(`Connected to colibri server on ${this.server}`);\r\n this.hasReportedOutage = false;\r\n this.waitForServerHello();\r\n\r\n // The server relays without replaying, so whatever it relayed while this client was away is\r\n // lost. Whoever has state to catch up on is told now. Socket.IO has already flushed what\r\n // was queued while disconnected, so a catch-up goes out after it.\r\n if (this.hasConnected) colibriReconnected(this);\r\n this.hasConnected = true;\r\n }\r\n\r\n // Until the next connect, sends wait in Socket.IO's buffer and go out ahead of anything a\r\n // catch-up sends, which is the wrong order for some of them.\r\n private onSocketDisconnect() {\r\n colibriDisconnected(this);\r\n }\r\n\r\n // Socket.IO retries silently, so a mistyped address, a server that is down and a firewall all\r\n // look like a slow connection. Reported once per outage, on its first failed attempt, not on\r\n // every retry.\r\n private onSocketConnectError(error: Error & { description?: unknown }) {\r\n if (this.hasReportedOutage) return;\r\n this.hasReportedOutage = true;\r\n\r\n // Under Node the message is only 'websocket error'; the cause (ECONNREFUSED, ENOTFOUND) is\r\n // the underlying error's. A browser hides it.\r\n const cause = (error.description as { message?: unknown } | undefined)?.message;\r\n const reason = typeof cause === 'string' && cause.length > 0 ? `${error.message}: ${cause}` : error.message;\r\n\r\n console.warn(\r\n `Colibri: could not connect to ${this.uri} (${reason}). ` +\r\n (this.socket.active\r\n ? 'Retrying until it answers - check the server address and that the server is running.'\r\n : 'Not retrying.')\r\n );\r\n }\r\n\r\n /*\r\n * Detecting a server that predates the protocol version check.\r\n *\r\n * Such a server neither refuses this client nor says what it speaks, so a current one\r\n * announces itself unprompted and the missing announcement is the signal. The timer only\r\n * observes; it does not gate message delivery.\r\n */\r\n\r\n private waitForServerHello() {\r\n if (this.hasReportedOldServer) return;\r\n\r\n clearTimeout(this.oldServerTimer);\r\n this.oldServerTimer = setTimeout(() => {\r\n this.onServerHelloMissing();\r\n }, OLD_SERVER_TIMEOUT_MS);\r\n }\r\n\r\n private stopWaitingForServerHello() {\r\n clearTimeout(this.oldServerTimer);\r\n this.oldServerTimer = undefined;\r\n }\r\n\r\n private onServerHelloMissing() {\r\n // A disconnected socket explains the silence by itself, and a reconnect re-arms this.\r\n if (!this.socket.connected) return;\r\n\r\n // A frozen or backgrounded tab stops draining the socket while timers keep running, so this\r\n // can fire ahead of the queued announcement. Wait another window.\r\n //\r\n // `typeof` rather than `document?.`: under Node `document` is undeclared, which throws a\r\n // ReferenceError that optional chaining does not catch.\r\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\r\n this.waitForServerHello();\r\n return;\r\n }\r\n\r\n this.hasReportedOldServer = true;\r\n this.stopWaitingForServerHello();\r\n\r\n const error = new ProtocolMismatchError(\r\n `The server did not identify itself within ${OLD_SERVER_TIMEOUT_MS / 1000}s, so it predates ` +\r\n `colibri-server 2.0.0 and speaks protocol v1; this client speaks v${PROTOCOL_VERSION}.`,\r\n // A protocol version, as in the refusal. Inferred, not received: every 2.0.0+ server\r\n // announces itself, and everything older speaks v1.\r\n OLD_SERVER_PROTOCOL_VERSION,\r\n PROTOCOL_VERSION,\r\n false\r\n );\r\n\r\n // Not disconnected: the Socket.IO envelope is the same in v1 and v2, so the connection works.\r\n // Tearing it down over a suspicion would turn a warning into an outage.\r\n console.warn(`Colibri: ${error.message} The connection still works; upgrade the server when you can.`);\r\n this.protocolMismatchSubject.next(error);\r\n }\r\n\r\n private onSocketAny(channel: string, msg: unknown) {\r\n colibriHeardFrom(this);\r\n const { command, payload } = msg as Pick<Message, 'command' | 'payload'>;\r\n\r\n // Colibri's own plumbing: kept off `messages` so applications need not recognize it.\r\n if (channel === COLIBRI_CHANNEL && command === PROTOCOL_REJECTED_COMMAND) {\r\n this.onProtocolRejected(payload as ProtocolRejection | undefined);\r\n return;\r\n }\r\n\r\n // The server announcing itself; kept off `messages` likewise.\r\n if (channel === COLIBRI_CHANNEL && command === PROTOCOL_ACCEPTED_COMMAND) {\r\n this.stopWaitingForServerHello();\r\n return;\r\n }\r\n\r\n this.messageSubject.next({ channel, command, payload });\r\n }\r\n\r\n // `rejection` is optional because it comes off the wire, and a bare `protocol::rejected` must\r\n // still give a usable error rather than a TypeError.\r\n private onProtocolRejected(rejection: ProtocolRejection | undefined) {\r\n // The server has said what is wrong; the old-server guess five seconds later would contradict it.\r\n this.stopWaitingForServerHello();\r\n this.hasReportedOldServer = true;\r\n\r\n // A mismatch cannot resolve itself; retrying only buries the log line that explains it.\r\n this.socket.io.reconnection(false);\r\n this.socket.disconnect();\r\n\r\n // A second refusal repeats the first: hang up, say nothing.\r\n if (this.hasReportedRefusal) return;\r\n this.hasReportedRefusal = true;\r\n\r\n const serverVersion = rejection?.serverVersion ?? 'unknown';\r\n const error = new ProtocolMismatchError(\r\n rejection?.reason ??\r\n `Server refused the connection: it speaks protocol v${serverVersion}, this client speaks v${PROTOCOL_VERSION}.`,\r\n serverVersion,\r\n rejection?.clientVersion ?? PROTOCOL_VERSION\r\n );\r\n\r\n console.error(\r\n `Colibri: ${error.message} Update colibri-web and colibri-server to matching versions. Not reconnecting.`\r\n );\r\n this.protocolMismatchSubject.next(error);\r\n }\r\n\r\n /**\r\n * Returns the existing Colibri instance or null if there's none yet\r\n * @param warnIfNotInitialized true => print Log message if Colibri has not been initialized yet\r\n * @returns\r\n */\r\n public static getInstance(warnIfNotInitialized: boolean = true): Colibri | null {\r\n if (warnIfNotInitialized && !Colibri.instance) {\r\n console.warn('Colibri not initialized yet! (Instance is null)');\r\n }\r\n return Colibri.instance;\r\n }\r\n\r\n /**\r\n * Sends a single message in the given `channel`.\r\n * @param channel message channel\r\n * @param command message command\r\n * @param payload message payload\r\n */\r\n public sendMessage(channel: string, command: string, payload: unknown = {}) {\r\n this.socket.emit(channel, {\r\n command,\r\n payload\r\n });\r\n }\r\n\r\n // #region Socket Events\r\n /**\r\n * Adds a `handler` function listening for messages in `channel`.\r\n * @param channel message channel\r\n * @param handler handler to be executed when a message is received\r\n */\r\n public registerChannel(channel: string, handler: (payload: Message) => void) {\r\n this.socket.on(channel, handler);\r\n }\r\n\r\n /**\r\n * Removes the `handler` function listening for messages in `channel`.\r\n * @param channel message channel\r\n * @param handler handler to be executed when a message is received\r\n */\r\n public unregisterChannel(channel: string, handler: (payload: Message) => void) {\r\n this.socket.off(channel, handler);\r\n }\r\n\r\n /**\r\n * Adds a one-time `handler` function listening for the next message in `channel`.\r\n * @param channel message channel\r\n * @param handler handler to be executed when a message is received\r\n */\r\n public registerOnce(channel: string, handler: (payload: Message) => void) {\r\n this.socket.once(channel, handler);\r\n }\r\n // #endregion\r\n\r\n // #region Rest API\r\n /**\r\n * Returns the REST API Endpoint for a given `key`, or null if the key is empty, `.` or `..`.\r\n * Any other key is stored under exactly that name (`#`, `?`, `/`, `%` and spaces included)\r\n * after surrounding whitespace and leading slashes are stripped.\r\n * @param key REST API storage key\r\n * @returns\r\n */\r\n public getRestUri(key: string): string | null {\r\n key = key.trim();\r\n while (key.startsWith('/')) key = key.substring(1);\r\n\r\n // A URL parser resolves a path segment of '.' or '..', percent-encoded or not, as a\r\n // directory step, so '..' would address the app's parent instead of a key.\r\n if (key.length === 0 || key === '.' || key === '..') return null;\r\n\r\n // Encoded because the key is one path segment: raw, '#' and '?' would cut it short, '/' would\r\n // split it, and '%' would not decode on the server.\r\n return this.uriRestApi + encodeURIComponent(key);\r\n }\r\n\r\n /**\r\n * Queries an object from the REST API identified by `key`.\r\n *\r\n * Rejects when the server cannot be reached (wrong address, server down, no network) or an\r\n * answer with a success status is not JSON.\r\n * @param key REST API storage key\r\n * @returns the stored value; or null if `key` is empty or the server answered with an error\r\n * status, such as 404 for a key that was never stored. A stored `null` looks the same.\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n public async getRestObject(key: string): Promise<any> {\r\n const uri = this.getRestUri(key);\r\n if (!uri) return null;\r\n\r\n const response = await fetch(uri, {\r\n method: 'GET',\r\n headers: {\r\n Accept: REST_MEDIA_TYPE\r\n }\r\n });\r\n\r\n if (response.status >= 400) return null;\r\n else return response.json();\r\n }\r\n\r\n /**\r\n * Sets or updates an object in the REST API identified by `key`.\r\n *\r\n * Rejects when the server cannot be reached (wrong address, server down, no network) or\r\n * `data` cannot be serialized to JSON (circular structure, BigInt).\r\n * @param key REST API storage key\r\n * @param data JSON data to write\r\n * @returns true if the server stored the data; false if `key` is empty or the server answered\r\n * with any other status\r\n */\r\n public async setRestObject(key: string, data: unknown): Promise<boolean> {\r\n const uri = this.getRestUri(key);\r\n if (!uri) return false;\r\n\r\n const response = await fetch(uri, {\r\n method: 'PUT',\r\n headers: {\r\n Accept: REST_MEDIA_TYPE,\r\n 'Content-Type': REST_MEDIA_TYPE\r\n },\r\n body: JSON.stringify(data)\r\n });\r\n\r\n return response.status >= 200 && response.status < 300;\r\n }\r\n\r\n /**\r\n * Deletes the object identified by `key` from the REST API.\r\n *\r\n * Rejects when the server cannot be reached (wrong address, server down, no network).\r\n * @param key REST API storage key\r\n * @returns true if the server deleted the value; false if `key` is empty or the server answered\r\n * with any other status, such as 404 for a key that holds no value\r\n */\r\n public async deleteRestObject(key: string): Promise<boolean> {\r\n const uri = this.getRestUri(key);\r\n if (!uri) return false;\r\n\r\n const response = await fetch(uri, {\r\n method: 'DELETE',\r\n headers: {\r\n Accept: REST_MEDIA_TYPE\r\n }\r\n });\r\n\r\n return response.status >= 200 && response.status < 300;\r\n }\r\n // #endregion\r\n}\r\n\r\n/////////////////////////////////////\r\n/// Wrapper Functions\r\n// (also for backward compatibility)\r\n/////////////////////////////////////\r\n\r\n/**\r\n * @see {@link Colibri.sendMessage `Colibri.sendMessage()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const SendMessage = (channel: string, command: string, payload: unknown = {}) =>\r\n Colibri.getInstance()?.sendMessage(channel, command, payload);\r\n\r\n/**\r\n * @see {@link Colibri.registerChannel `Colibri.registerChannel()`}\r\n *\r\n * Works before `new Colibri()` too: the handler is attached once the instance is constructed.\r\n */\r\nexport const RegisterChannel = (channel: string, handler: (payload: Message) => void): void => {\r\n const colibri = Colibri.getInstance(false);\r\n if (colibri) colibri.registerChannel(channel, handler);\r\n else pendingRegistrations.push({ channel, handler, once: false });\r\n};\r\n\r\n/**\r\n * @see {@link Colibri.unregisterChannel `Colibri.unregisterChannel()`}\r\n *\r\n * Before `new Colibri()`, takes back a handler that {@link RegisterChannel} or\r\n * {@link RegisterOnce} queued.\r\n */\r\nexport const UnregisterChannel = (channel: string, handler: (payload: Message) => void): void => {\r\n const colibri = Colibri.getInstance(false);\r\n if (colibri) {\r\n colibri.unregisterChannel(channel, handler);\r\n return;\r\n }\r\n\r\n // Removes one registration per call, like Socket.IO's off().\r\n const index = pendingRegistrations.findIndex(p => p.channel === channel && p.handler === handler);\r\n if (index >= 0) pendingRegistrations.splice(index, 1);\r\n};\r\n\r\n/**\r\n * @see {@link Colibri.registerOnce `Colibri.registerOnce()`}\r\n *\r\n * Works before `new Colibri()` too, like {@link RegisterChannel}.\r\n */\r\nexport const RegisterOnce = (channel: string, handler: (payload: Message) => void): void => {\r\n const colibri = Colibri.getInstance(false);\r\n if (colibri) colibri.registerOnce(channel, handler);\r\n else pendingRegistrations.push({ channel, handler, once: true });\r\n};\r\n\r\n/**\r\n * @see {@link Colibri.getRestObject `Colibri.getRestObject()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const GetRestApi = (key: string) => Colibri.getInstance()?.getRestObject(key);\r\n\r\n/**\r\n * @see {@link Colibri.setRestObject `Colibri.setRestObject()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const PutRestApi = (key: string, data: unknown) => Colibri.getInstance()?.setRestObject(key, data);\r\n\r\n/**\r\n * @see {@link Colibri.deleteRestObject `Colibri.deleteRestObject()`}\r\n * @returns undefined if {@link Colibri `Colibri`} has not been initialized yet\r\n */\r\nexport const DeleteRestApi = (key: string) => Colibri.getInstance()?.deleteRestObject(key);\r\n","import { CustomError } from 'ts-custom-error';\r\n\r\n/** Base class of colibri-web's errors, such as an invalid address or port. */\r\nexport class ColibriError extends CustomError {}\r\n\r\n/**\r\n * Reported when this client and the server do not speak the same wire protocol. The fix is to run\r\n * matching `colibri-web` and `colibri-server` versions.\r\n *\r\n * Check `fatal` first. Both kinds are reported on `Colibri.protocolMismatch`:\r\n *\r\n * - `fatal: true`: the server refused this client and disconnected it. Terminal. There is no\r\n * negotiation, so reconnecting cannot succeed, and `Colibri` stops Socket.IO from retrying.\r\n * - `fatal: false`: the server is suspected to predate colibri-server 2.0.0, because it never\r\n * announced its protocol version. The connection stays up. Upgrade the server when you can.\r\n */\r\nexport class ProtocolMismatchError extends ColibriError {\r\n public constructor(\r\n message: string,\r\n /**\r\n * Wire protocol version the server speaks, like `'2'`, not a release version. The server\r\n * reports it on refusal, and it is `'unknown'` if the refusal did not say. When not `fatal`\r\n * it is inferred: a server that never announced itself is taken to speak `'1'`.\r\n */\r\n public readonly serverVersion: string,\r\n /** Wire protocol version this client announced in its handshake. */\r\n public readonly clientVersion: string,\r\n /** Whether the connection is gone. False when the server is only suspected to be old. */\r\n public readonly fatal: boolean = true\r\n ) {\r\n super(message);\r\n }\r\n}\r\n","import type { Colibri } from './Colibri';\r\n\r\n/*\r\n * Internal, not exported from index.ts. Lets RegisterModelSync and RemoteLogger, which can run\r\n * before `new Colibri()`, hook into an instance's life.\r\n *\r\n * Only a type is imported from Colibri, so the modules do not depend on each other at runtime.\r\n * Callers check Colibri.getInstance(false) first and only queue when there is none.\r\n */\r\n\r\ntype InstanceAction = (colibri: Colibri) => void;\r\n\r\nconst awaitingInstance: InstanceAction[] = [];\r\n\r\n/** Runs `action` with the instance once `new Colibri()` has constructed it. Returns a function that unqueues it. */\r\nexport const whenColibriCreated = (action: InstanceAction): (() => void) => {\r\n awaitingInstance.push(action);\r\n return () => {\r\n const index = awaitingInstance.indexOf(action);\r\n if (index >= 0) awaitingInstance.splice(index, 1);\r\n };\r\n};\r\n\r\n/** Runs everything {@link whenColibriCreated} queued, in order. Called by the Colibri constructor. */\r\nexport const colibriCreated = (colibri: Colibri): void => {\r\n for (const action of awaitingInstance.splice(0)) {\r\n // A throwing action must neither stop the others nor escape from `new Colibri()`.\r\n try {\r\n action(colibri);\r\n } catch (error) {\r\n console.error('Colibri: a registration made before new Colibri() failed to attach.', error);\r\n }\r\n }\r\n};\r\n\r\n// Per instance: an action is bound to its connection, and a WeakMap lets both go with the instance.\r\nconst reconnectActions = new WeakMap<Colibri, InstanceAction[]>();\r\nconst disconnectActions = new WeakMap<Colibri, InstanceAction[]>();\r\n\r\nconst addAction = (actions: WeakMap<Colibri, InstanceAction[]>, colibri: Colibri, action: InstanceAction) => {\r\n const forInstance = actions.get(colibri);\r\n if (forInstance) forInstance.push(action);\r\n else actions.set(colibri, [action]);\r\n};\r\n\r\nconst runActions = (actions: WeakMap<Colibri, InstanceAction[]>, colibri: Colibri, failure: string) => {\r\n for (const action of actions.get(colibri) ?? []) {\r\n try {\r\n action(colibri);\r\n } catch (error) {\r\n console.error(failure, error);\r\n }\r\n }\r\n};\r\n\r\n/** Runs `action` on each connect of `colibri` after its first, when what the server relayed in between has been missed. */\r\nexport const onColibriReconnected = (colibri: Colibri, action: InstanceAction): void => {\r\n addAction(reconnectActions, colibri, action);\r\n};\r\n\r\n/** Runs everything {@link onColibriReconnected} registered for `colibri`, in order. Called by Colibri. */\r\nexport const colibriReconnected = (colibri: Colibri): void => {\r\n runActions(reconnectActions, colibri, 'Colibri: catching up after a reconnect failed.');\r\n};\r\n\r\n/**\r\n * Runs `action` whenever `colibri` loses its connection. Until the next connect, sends wait in\r\n * Socket.IO's buffer and go out ahead of anything a catch-up sends.\r\n */\r\nexport const onColibriDisconnected = (colibri: Colibri, action: InstanceAction): void => {\r\n addAction(disconnectActions, colibri, action);\r\n};\r\n\r\n/** Runs everything {@link onColibriDisconnected} registered for `colibri`, in order. Called by Colibri. */\r\nexport const colibriDisconnected = (colibri: Colibri): void => {\r\n runActions(disconnectActions, colibri, 'Colibri: handling a disconnect failed.');\r\n};\r\n\r\nconst lastHeard = new WeakMap<Colibri, number>();\r\n\r\n/** Notes that `colibri` just received something from the server. Called by Colibri. */\r\nexport const colibriHeardFrom = (colibri: Colibri): void => {\r\n lastHeard.set(colibri, Date.now());\r\n};\r\n\r\n/**\r\n * When `colibri` last received anything from the server (`Date.now()`), or undefined if never.\r\n * The server's 100 ms latency ping keeps this fresh on a working connection, so after an outage it\r\n * is when the connection actually died; Socket.IO notices a connection that died without closing much later.\r\n */\r\nexport const lastHeardFrom = (colibri: Colibri): number | undefined => lastHeard.get(colibri);\r\n","import { RegisterChannel, SendMessage, UnregisterChannel } from './Colibri';\r\n\r\n/**\r\n * A colour on the wire: `[r, g, b, a]` with components 0-1 from colibri-web, `\"#RRGGBBAA\"` from\r\n * colibri-unity. Received colours can be either; normalize with {@link toHexColor} or\r\n * {@link toRgbaColor}.\r\n */\r\nexport type ColorValue = string | [number, number, number, number];\r\n\r\nconst sendBool = (channel: string, val: boolean) => {\r\n SendMessage(channel, 'broadcast::bool', val);\r\n};\r\n\r\nconst sendBoolArray = (channel: string, val: boolean[]) => {\r\n SendMessage(channel, 'broadcast::bool[]', val);\r\n};\r\n\r\nconst sendNumber = (channel: string, val: number) => {\r\n SendMessage(channel, 'broadcast::float', val);\r\n};\r\n\r\nconst sendNumberArray = (channel: string, val: number[]) => {\r\n SendMessage(channel, 'broadcast::float[]', val);\r\n};\r\n\r\nconst sendString = (channel: string, val: string) => {\r\n SendMessage(channel, 'broadcast::string', val);\r\n};\r\n\r\nconst sendStringArray = (channel: string, val: string[]) => {\r\n SendMessage(channel, 'broadcast::string[]', val);\r\n};\r\n\r\nconst sendVector2 = (channel: string, val: [number, number]) => {\r\n SendMessage(channel, 'broadcast::vector2', val);\r\n};\r\n\r\nconst sendVector2Array = (channel: string, val: [number, number][]) => {\r\n SendMessage(channel, 'broadcast::vector2[]', val);\r\n};\r\n\r\nconst sendVector3 = (channel: string, val: [number, number, number]) => {\r\n SendMessage(channel, 'broadcast::vector3', val);\r\n};\r\n\r\nconst sendVector3Array = (channel: string, val: [number, number, number][]) => {\r\n SendMessage(channel, 'broadcast::vector3[]', val);\r\n};\r\n\r\nconst sendQuaternion = (channel: string, val: [number, number, number, number]) => {\r\n SendMessage(channel, 'broadcast::quaternion', val);\r\n};\r\n\r\nconst sendQuaternionArray = (channel: string, val: [number, number, number, number][]) => {\r\n SendMessage(channel, 'broadcast::quaternion[]', val);\r\n};\r\n\r\nconst sendColor = (channel: string, val: ColorValue) => {\r\n SendMessage(channel, 'broadcast::color', val);\r\n};\r\n\r\nconst sendColorArray = (channel: string, val: ColorValue[]) => {\r\n SendMessage(channel, 'broadcast::color[]', val);\r\n};\r\n\r\nconst sendJson = (channel: string, val: { [key: string]: unknown }) => {\r\n SendMessage(channel, 'broadcast::json', val);\r\n};\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\ntype genericCallback = (val: any) => void;\r\nconst listeners: Partial<Record<string, Partial<Record<string, genericCallback[]>>>> = {};\r\nconst channelHandlers: Partial<Record<string, Parameters<typeof RegisterChannel>[1]>> = {};\r\n\r\n// NOTE: T pins the payload type the call site's callback expects\r\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\r\nconst registerListener = <T>(channel: string, type: string, callback: (val: T) => void) => {\r\n let channelListeners = listeners[channel];\r\n if (channelListeners === undefined) {\r\n const newChannelListeners: Partial<Record<string, genericCallback[]>> = {};\r\n channelListeners = newChannelListeners;\r\n listeners[channel] = newChannelListeners;\r\n const handler: Parameters<typeof RegisterChannel>[1] = msg => {\r\n const commandListeners = newChannelListeners[msg.command];\r\n if (commandListeners !== undefined) {\r\n commandListeners.forEach(cb => {\r\n cb(msg.payload);\r\n });\r\n }\r\n };\r\n channelHandlers[channel] = handler;\r\n RegisterChannel(channel, handler);\r\n }\r\n\r\n let typeListeners = channelListeners[type];\r\n if (typeListeners === undefined) {\r\n typeListeners = [];\r\n channelListeners[type] = typeListeners;\r\n }\r\n\r\n typeListeners.push(callback);\r\n};\r\n\r\nconst receiveBool = (channel: string, callback: (val: boolean) => void) => {\r\n registerListener<boolean>(channel, 'broadcast::bool', callback);\r\n};\r\n\r\nconst receiveBoolArray = (channel: string, callback: (val: boolean[]) => void) => {\r\n registerListener<boolean[]>(channel, 'broadcast::bool[]', callback);\r\n};\r\n\r\n// Unity tags integers as broadcast::int (`Sync.Send(channel, 5)`), so listen for both.\r\nconst receiveNumber = (channel: string, callback: (val: number) => void) => {\r\n registerListener<number>(channel, 'broadcast::float', callback);\r\n registerListener<number>(channel, 'broadcast::int', callback);\r\n};\r\n\r\nconst receiveNumberArray = (channel: string, callback: (val: number[]) => void) => {\r\n registerListener<number[]>(channel, 'broadcast::float[]', callback);\r\n registerListener<number[]>(channel, 'broadcast::int[]', callback);\r\n};\r\n\r\nconst receiveString = (channel: string, callback: (val: string) => void) => {\r\n registerListener<string>(channel, 'broadcast::string', callback);\r\n};\r\n\r\nconst receiveStringArray = (channel: string, callback: (val: string[]) => void) => {\r\n registerListener<string[]>(channel, 'broadcast::string[]', callback);\r\n};\r\n\r\nconst receiveVector2 = (channel: string, callback: (val: [number, number]) => void) => {\r\n registerListener<[number, number]>(channel, 'broadcast::vector2', callback);\r\n};\r\n\r\nconst receiveVector2Array = (channel: string, callback: (val: [number, number][]) => void) => {\r\n registerListener<[number, number][]>(channel, 'broadcast::vector2[]', callback);\r\n};\r\n\r\nconst receiveVector3 = (channel: string, callback: (val: [number, number, number]) => void) => {\r\n registerListener<[number, number, number]>(channel, 'broadcast::vector3', callback);\r\n};\r\n\r\nconst receiveVector3Array = (channel: string, callback: (val: [number, number, number][]) => void) => {\r\n registerListener<[number, number, number][]>(channel, 'broadcast::vector3[]', callback);\r\n};\r\n\r\nconst receiveQuaternion = (channel: string, callback: (val: [number, number, number, number]) => void) => {\r\n registerListener<[number, number, number, number]>(channel, 'broadcast::quaternion', callback);\r\n};\r\n\r\nconst receiveQuaternionArray = (channel: string, callback: (val: [number, number, number, number][]) => void) => {\r\n registerListener<[number, number, number, number][]>(channel, 'broadcast::quaternion[]', callback);\r\n};\r\n\r\n// The payload is a \"#RRGGBBAA\" string from Unity but an [r,g,b,a] array from colibri-web.\r\nconst receiveColor = (channel: string, callback: (val: ColorValue) => void) => {\r\n registerListener<ColorValue>(channel, 'broadcast::color', callback);\r\n};\r\n\r\nconst receiveColorArray = (channel: string, callback: (val: ColorValue[]) => void) => {\r\n registerListener<ColorValue[]>(channel, 'broadcast::color[]', callback);\r\n};\r\n\r\nconst receiveJson = (channel: string, callback: (val: { [key: string]: unknown }) => void) => {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n registerListener<any>(channel, 'broadcast::json', callback);\r\n};\r\n\r\n// Colour normalization. A malformed colour warns and falls back to opaque black instead of\r\n// throwing, like colibri-unity's JsonExtensions, so it cannot break the channel handler.\r\n\r\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\r\nconst hexPair = (n: number) =>\r\n Math.round(clamp01(n) * 255)\r\n .toString(16)\r\n .padStart(2, '0');\r\n\r\nconst warnNotAColor = (val: unknown) => {\r\n console.warn(`Colibri: '${JSON.stringify(val)}' is not a colour - expected \"#RRGGBBAA\" or [r,g,b,a]. Using black.`);\r\n};\r\n\r\n// Takes `unknown`: this validates wire data, so the declared ColorValue must not be trusted.\r\nconst asComponents = (val: unknown): number[] | null => {\r\n if (!Array.isArray(val) || val.length < 3) return null;\r\n const numbers = val as unknown[];\r\n if (!numbers.every(c => typeof c === 'number' && !Number.isNaN(c))) return null;\r\n return numbers as number[];\r\n};\r\n\r\n/** Converts a colour to `\"#RRGGBBAA\"`. An unparseable colour warns and gives opaque black. */\r\nexport const toHexColor = (val: ColorValue): string => {\r\n const rgba = toRgbaColor(val);\r\n return `#${hexPair(rgba[0])}${hexPair(rgba[1])}${hexPair(rgba[2])}${hexPair(rgba[3])}`;\r\n};\r\n\r\n/** Converts a colour to `[r, g, b, a]` with components 0-1. An unparseable colour warns and gives opaque black. */\r\nexport const toRgbaColor = (val: ColorValue): [number, number, number, number] => {\r\n const components = asComponents(val);\r\n if (components !== null) {\r\n return [\r\n clamp01(components[0]),\r\n clamp01(components[1]),\r\n clamp01(components[2]),\r\n components.length > 3 ? clamp01(components[3]) : 1\r\n ];\r\n }\r\n\r\n if (typeof val === 'string') {\r\n const hex = val.startsWith('#') ? val.slice(1) : val;\r\n // Expand the #RGB and #RGBA shorthands that Unity's ColorUtility.TryParseHtmlString accepts.\r\n const digits = hex.length === 3 || hex.length === 4 ? hex.replace(/./g, c => c + c) : hex;\r\n\r\n if ((digits.length === 6 || digits.length === 8) && /^[0-9a-f]+$/i.test(digits)) {\r\n const byteAt = (i: number) => parseInt(digits.slice(i, i + 2), 16) / 255;\r\n return [byteAt(0), byteAt(2), byteAt(4), digits.length === 8 ? byteAt(6) : 1];\r\n }\r\n }\r\n\r\n warnNotAColor(val);\r\n return [0, 0, 0, 1];\r\n};\r\n\r\nconst unregister = (channel: string, callback: genericCallback) => {\r\n const channelListeners = listeners[channel];\r\n if (channelListeners === undefined) return;\r\n\r\n let hasRemainingListeners = false;\r\n for (const command in channelListeners) {\r\n const commandListeners = channelListeners[command];\r\n const index = commandListeners?.indexOf(callback) ?? -1;\r\n if (index >= 0) {\r\n commandListeners?.splice(index, 1);\r\n }\r\n if ((commandListeners?.length ?? 0) > 0) {\r\n hasRemainingListeners = true;\r\n }\r\n }\r\n\r\n if (!hasRemainingListeners) {\r\n const handler = channelHandlers[channel];\r\n if (handler !== undefined) {\r\n UnregisterChannel(channel, handler);\r\n }\r\n Reflect.deleteProperty(listeners, channel);\r\n Reflect.deleteProperty(channelHandlers, channel);\r\n }\r\n};\r\n\r\n/** Sends typed values on a channel and registers callbacks for the values received on it. */\r\nexport const Sync = {\r\n sendBool,\r\n sendBoolArray,\r\n sendNumber,\r\n sendNumberArray,\r\n sendString,\r\n sendStringArray,\r\n sendVector2,\r\n sendVector2Array,\r\n sendVector3,\r\n sendVector3Array,\r\n sendQuaternion,\r\n sendQuaternionArray,\r\n sendColor,\r\n sendColorArray,\r\n sendJson,\r\n\r\n receiveBool,\r\n receiveBoolArray,\r\n receiveNumber,\r\n receiveNumberArray,\r\n receiveString,\r\n receiveStringArray,\r\n receiveVector2,\r\n receiveVector2Array,\r\n receiveVector3,\r\n receiveVector3Array,\r\n receiveQuaternion,\r\n receiveQuaternionArray,\r\n receiveColor,\r\n receiveColorArray,\r\n receiveJson,\r\n\r\n unregister,\r\n\r\n // for better compatibility with Unity Colibri\r\n // sendInt and sendIntArray still send the float commands (JS cannot tell 5 from 5.0), so a\r\n // Unity client has to listen for float, not int.\r\n sendFloat: sendNumber,\r\n sendInt: sendNumber,\r\n sendFloatArray: sendNumberArray,\r\n sendIntArray: sendNumberArray\r\n};\r\n","import { Colibri } from './Colibri';\r\nimport { whenColibriCreated } from './lifecycle';\r\n\r\ntype LogLevel = 'error' | 'warn' | 'info' | 'debug';\r\n\r\n// Lines logged before `new Colibri()` are kept up to this many and sent once it exists. The first\r\n// ones are kept, since the first error at startup is usually the cause of the rest; later ones are only counted.\r\nconst MAX_EARLY_LINES = 100;\r\n\r\nexport class RemoteLogger {\r\n // The instance that patched the console. Later instances delegate to it, since patching again\r\n // would forward every line once per instance (StrictMode and hot reload create extras).\r\n private static patched: RemoteLogger | null = null;\r\n\r\n // Itself, or the instance that patched the console.\r\n private readonly forwarder: RemoteLogger;\r\n private hasWarnedOfAnotherInstance = false;\r\n\r\n private readonly consoleDebug = console.debug;\r\n private readonly consoleLog = console.log;\r\n private readonly consoleInfo = console.info;\r\n private readonly consoleWarn = console.warn;\r\n private readonly consoleError = console.error;\r\n\r\n // Recursion guard: lines logged while forwarding (by Colibri, or by a getter that JSON.stringify\r\n // runs) go to the console only.\r\n private forwarding = false;\r\n\r\n // Lines logged before `new Colibri()`.\r\n private readonly earlyLines: { level: LogLevel; message: string }[] = [];\r\n private droppedEarlyLines = 0;\r\n private waitingForColibri = false;\r\n\r\n /**\r\n * @deprecated use new class constructor directly\r\n */\r\n public static init(enabled: boolean = true) {\r\n return new RemoteLogger(enabled);\r\n }\r\n\r\n /**\r\n * Forwards everything logged through `console` to the server's log. Create one, at startup.\r\n * A further instance warns once and does not patch again; its `enabled` argument, `enable()` and\r\n * `disable()` act on the first one.\r\n * @param enabled whether to forward from the start\r\n */\r\n public constructor(private enabled: boolean = true) {\r\n const patched = RemoteLogger.patched;\r\n if (patched) {\r\n this.forwarder = patched;\r\n patched.enabled = enabled;\r\n if (!patched.hasWarnedOfAnotherInstance) {\r\n patched.hasWarnedOfAnotherInstance = true;\r\n console.warn(\r\n 'RemoteLogger: the console is already forwarded by an earlier new RemoteLogger(), so this ' +\r\n 'one controls that instead of forwarding every line a second time. Create one, at startup.'\r\n );\r\n }\r\n return;\r\n }\r\n\r\n RemoteLogger.patched = this;\r\n this.forwarder = this;\r\n\r\n // intercept calls from console\r\n\r\n console.debug = (...args: unknown[]) => {\r\n this.consoleDebug(...args);\r\n this.sendMessage('debug', args);\r\n };\r\n\r\n console.log = (...args: unknown[]) => {\r\n this.consoleLog(...args);\r\n this.sendMessage('info', args);\r\n };\r\n\r\n console.info = (...args: unknown[]) => {\r\n this.consoleInfo(...args);\r\n this.sendMessage('info', args);\r\n };\r\n\r\n console.warn = (...args: unknown[]) => {\r\n this.consoleWarn(...args);\r\n this.sendMessage('warn', args);\r\n };\r\n\r\n console.error = (...args: unknown[]) => {\r\n this.consoleError(...args);\r\n this.sendMessage('error', args);\r\n };\r\n }\r\n\r\n private sendMessage(level: LogLevel, args: unknown[]) {\r\n if (!this.enabled || this.forwarding) return;\r\n\r\n this.forwarding = true;\r\n try {\r\n const message = [...args].map(stringify).join().trim();\r\n\r\n // Not SendMessage(), which warns when there is no instance.\r\n const colibri = Colibri.getInstance(false);\r\n if (colibri) colibri.sendMessage('log', level, message);\r\n else this.keepUntilColibriExists(level, message);\r\n } catch (error) {\r\n // Forwarding must not make console.log throw into the caller; the line is already printed.\r\n this.consoleError('RemoteLogger: could not forward a log line.', error);\r\n } finally {\r\n this.forwarding = false;\r\n }\r\n }\r\n\r\n private keepUntilColibriExists(level: LogLevel, message: string) {\r\n if (this.earlyLines.length >= MAX_EARLY_LINES) {\r\n this.droppedEarlyLines++;\r\n return;\r\n }\r\n\r\n this.earlyLines.push({ level, message });\r\n if (!this.waitingForColibri) {\r\n this.waitingForColibri = true;\r\n whenColibriCreated(colibri => {\r\n this.sendEarlyLines(colibri);\r\n });\r\n }\r\n }\r\n\r\n // Socket.IO buffers emits until connected, so there is no need to wait for the connection.\r\n private sendEarlyLines(colibri: Colibri) {\r\n this.waitingForColibri = false;\r\n\r\n for (const { level, message } of this.earlyLines.splice(0)) {\r\n colibri.sendMessage('log', level, message);\r\n }\r\n\r\n if (this.droppedEarlyLines > 0) {\r\n colibri.sendMessage(\r\n 'log',\r\n 'warn',\r\n `RemoteLogger: ${this.droppedEarlyLines} more line(s) logged before new Colibri() were not kept, only the first ${MAX_EARLY_LINES}.`\r\n );\r\n this.droppedEarlyLines = 0;\r\n }\r\n }\r\n\r\n /** Starts forwarding console output to the server's log. */\r\n public enable() {\r\n this.forwarder.enabled = true;\r\n }\r\n\r\n /** Stops forwarding to the server's log; the console still prints. */\r\n public disable() {\r\n this.forwarder.enabled = false;\r\n }\r\n}\r\n\r\nconst stringify = (obj: unknown): string => {\r\n if (obj instanceof Error) {\r\n return obj.message + '\\n' + (obj.stack ?? '');\r\n }\r\n\r\n if (typeof obj === 'string') return obj;\r\n\r\n const cache: unknown[] = [];\r\n let str: string;\r\n try {\r\n str = JSON.stringify(\r\n obj,\r\n (_, value: unknown) => {\r\n // JSON.stringify throws on a BigInt.\r\n if (typeof value === 'bigint') return value.toString();\r\n if (typeof value === 'object' && value !== null) {\r\n if (cache.indexOf(value) !== -1) {\r\n // Circular reference found, discard key\r\n return;\r\n }\r\n // Store value in our collection\r\n cache.push(value);\r\n }\r\n return value;\r\n },\r\n 2\r\n );\r\n } catch {\r\n // Anything else JSON cannot encode, e.g. a toJSON() that throws.\r\n str = String(obj);\r\n }\r\n return str;\r\n};\r\n","import { BehaviorSubject, Observable } from 'rxjs';\r\nimport { Colibri, Message, RegisterChannel, SendMessage } from './Colibri';\r\nimport { lastHeardFrom, onColibriDisconnected, onColibriReconnected, whenColibriCreated } from './lifecycle';\r\nimport { SyncModel } from './SyncModel';\r\n\r\n// Calls SyncModel's own toJson(), update() and detach(), as a model class may define methods of the same name.\r\nconst toJsonOf = <T>(model: SyncModel<T>, attributes?: string[]): Partial<T> =>\r\n SyncModel.prototype.toJson.call(model, attributes) as Partial<T>;\r\nconst updateOf = <T>(model: SyncModel<T>, updates: Partial<T>): void => {\r\n SyncModel.prototype.update.call(model, updates);\r\n};\r\nconst detachOf = (model: SyncModel<unknown> | undefined): void => {\r\n if (model) SyncModel.prototype.detach.call(model);\r\n};\r\n\r\ninterface ModelSyncMsg<T extends SyncModel<T>> extends Message {\r\n command: 'model::update' | 'model::delete';\r\n payload: Partial<T> & { id: string };\r\n}\r\n\r\ninterface ModelSyncRegistration<T> {\r\n /** The channel to sync on. Recommended: the default, the lowercased class name, changes when minified. */\r\n name?: string;\r\n type: { new (id: string): T };\r\n}\r\n\r\ntype ModelSync<T> = [Observable<T[]>, (model: T) => void];\r\n\r\n// Runs `action` now, or once `new Colibri()` has constructed the instance. SendMessage drops\r\n// messages sent before that, and a module-level RegisterModelSync usually runs first.\r\nconst withColibri = (action: (colibri: Colibri) => void) => {\r\n const colibri = Colibri.getInstance(false);\r\n if (colibri) action(colibri);\r\n else whenColibriCreated(action);\r\n};\r\n\r\n// Warn once per channel: the same type may be registered again, e.g. on every render.\r\nconst warnedMinifiedNames = new Set<string>();\r\n\r\n// Minifiers rename classes, typically to one or two letters or something with a '$', so a channel\r\n// derived from the class name stops matching other builds and Unity.\r\nconst warnIfMinified = (className: string, channel: string) => {\r\n if ((channel.length > 2 && !channel.includes('$')) || warnedMinifiedNames.has(channel)) return;\r\n warnedMinifiedNames.add(channel);\r\n console.warn(\r\n `Colibri: RegisterModelSync is syncing on channel '${channel}', derived from the class name ` +\r\n `'${className}', which looks minified. A minified build renames classes, so this will not ` +\r\n `sync with another build or with Unity. Pass the channel explicitly: ` +\r\n `RegisterModelSync({ name: '...', type: ${className || 'YourModel'} }).`\r\n );\r\n};\r\n\r\n// A model::update with nothing but the id is the server's answer for a model it does not have.\r\nconst isBare = (modelData: object) => Object.keys(modelData).every(key => key === 'id');\r\n\r\n// Per channel, each registration's check for an own model with a given id. Every registration on a\r\n// channel receives the answers to the others' requests, and a bare id may be one of those.\r\nconst ownOnChannel = new Map<string, Set<(id: string) => boolean>>();\r\n\r\n// Per channel, each registration's guard (see guardFrom): all of them receive the answers to the\r\n// others' requests.\r\nconst guardsOnChannel = new Map<string, Set<(end: string) => void>>();\r\n\r\n// Channel for the askForEnd requests, the same one colibri-unity uses after a reconnect. It never has models.\r\nconst END_CHANNEL = 'colibri::reconnect';\r\n\r\nlet endRequests = 0;\r\n\r\n// Requests an id nobody has, on a channel without models, and returns it. The server answers one\r\n// client's requests in order, so the bare-id answer marks the end of the answers to earlier requests.\r\nconst askForEnd = (colibri: Colibri) => {\r\n const id = `colibri-web-${++endRequests}`;\r\n colibri.sendMessage(END_CHANNEL, 'model::request', { id, again: true });\r\n return id;\r\n};\r\n\r\n// History kept per field to spot changes lost in a dead connection (see keepKnown, lostChanges).\r\n// An earlier value counts only if it was still current within KNOWN_VALUES_MS before the outage:\r\n// a change sent before that has arrived for certain.\r\nconst KNOWN_VALUES_KEPT = 8;\r\nconst KNOWN_VALUES_MS = 10_000;\r\n\r\n// How long updates for an id deleted here are ignored, as in colibri-unity's LocallyDeletedModels:\r\n// a remote update relayed before the server had the delete can still arrive after it. Later ones\r\n// are ignored by the server (MODEL_TOMBSTONE_SECONDS).\r\nconst DELETED_HERE_MS = 60_000;\r\n\r\n// Fallback for sendAfresh: how long updates for the id are dropped if the bare-id answer never comes.\r\nconst AFRESH_ANSWER_MS = 10_000;\r\n\r\nexport const RegisterModelSync = <T extends SyncModel<T>>(registration: ModelSyncRegistration<T>): ModelSync<T> => {\r\n const name = registration.name || registration.type.name.toLowerCase();\r\n if (!registration.name) warnIfMinified(registration.type.name, name);\r\n\r\n const models = new BehaviorSubject<T[]>([]);\r\n\r\n // Ids deleted here and when (see DELETED_HERE_MS), oldest first.\r\n const deletedHere = new Map<string, number>();\r\n const rememberDeletedHere = (id: string) => {\r\n const now = Date.now();\r\n for (const [old, at] of deletedHere) {\r\n if (now - at < DELETED_HERE_MS) break;\r\n deletedHere.delete(old);\r\n }\r\n deletedHere.delete(id);\r\n deletedHere.set(id, now);\r\n };\r\n // Ids deleted here and registered again, and when (see sendAfresh).\r\n const afresh = new Map<string, number>();\r\n\r\n const recentlyDeletedHere = (id: string) => {\r\n const at = deletedHere.get(id);\r\n if (at === undefined) return false;\r\n if (Date.now() - at < DELETED_HERE_MS) return true;\r\n deletedHere.delete(id);\r\n return false;\r\n };\r\n\r\n // Models passed to registerModel, as opposed to ones created from remote updates. Only these are\r\n // asked for by id and sent in full.\r\n const ownModels = new WeakSet<T>();\r\n\r\n // Own models asked for by id (registerModel, reconnect) and not answered yet, with the instance that asked.\r\n const awaitingAnswer = new Map<string, Colibri>();\r\n\r\n // Own models the server has answered for, on this or an earlier connection (see askFor).\r\n const answered = new WeakSet<T>();\r\n\r\n // Instance to send the request for every model through, until it is sent (see catchUpOnceAnswered).\r\n let catchingUpThrough: Colibri | undefined;\r\n\r\n // Ids deleted while their answer was, or may still have been, on its way (see forget). A bare-id\r\n // answer, e.g. from a server with MODEL_TOMBSTONE_SECONDS=0, must not become a model.\r\n const deletedWhileAwaited = new Set<string>();\r\n\r\n // Property names of changes to own models that are held back instead of sent: made while\r\n // disconnected or before the server's answer for the model (see registerModel).\r\n const heldChanges = new WeakMap<T, Set<string>>();\r\n\r\n // Changes waiting for SyncModel's 1 ms buffer to report them, so they go out together. A remote\r\n // update does not overwrite them (see applyUpdate).\r\n const changesToSend = new WeakMap<T, Set<string>>();\r\n\r\n const noteChangeToSend = (model: T, change: string) => {\r\n const toSend = changesToSend.get(model);\r\n if (toSend) toSend.add(change);\r\n else changesToSend.set(model, new Set([change]));\r\n };\r\n\r\n // Own models asked for again after sending their held changes (see sendOnTop), until the answers\r\n // to that are over (see endWait).\r\n interface Confirmation {\r\n sent: Map<string, string>; // JSON of the fields sent that differ from what the server showed\r\n replaced: Map<string, string | undefined>; // the server's JSON for those fields, if it had one\r\n props: string[]; // property names of the held changes\r\n shown: Map<string, unknown>; // latest value an update has shown for each sent field\r\n end: string; // askForEnd id\r\n }\r\n const confirming = new Map<string, Confirmation>();\r\n\r\n const endConfirmation = (id: string) => {\r\n const confirmation = confirming.get(id);\r\n confirming.delete(id);\r\n return confirmation;\r\n };\r\n\r\n // Per own model and field name (as sent), the values the field had, oldest first: the last one the\r\n // server showed (see applyUpdate), then those sent since that keepKnown keeps (see sendUpdate).\r\n interface KnownValue {\r\n json: string;\r\n at: number;\r\n sent: boolean; // sent by this client, otherwise shown by the server\r\n }\r\n const knownValues = new WeakMap<T, Map<string, KnownValue[]>>();\r\n\r\n // Own models asked for again on a reconnect, until the answers are over (see roundEnd). Their\r\n // updates are checked for changes lost in the dead connection (see lostChanges): per id, the\r\n // values shown, and the fields found lost, which keep their value.\r\n interface AfterOutage {\r\n shown: Map<string, unknown>;\r\n lost: Set<string>;\r\n }\r\n const askedAfterOutage = new Map<string, AfterOutage>();\r\n\r\n // While reconnect requests are unanswered: the askForEnd id sent after them (see takeAnswer).\r\n let roundEnd: string | undefined;\r\n\r\n // askForEnd ids whose answers end the window for stale updates (see guardFrom), oldest first.\r\n let guardEnds: string[] = [];\r\n\r\n // Per model, the field names (as sent) kept out of updates until the answer for the last guardEnds id\r\n // at the time they were sent. Longer would drop another client's later change to the same field.\r\n let keptOut = new WeakMap<T, Map<string, string>>();\r\n\r\n // Own models settled by an update that may not have been their answer (see takeAnswer), mapped to\r\n // the guardEnds id whose answer comes after theirs.\r\n const answerMayFollow = new Map<string, string>();\r\n\r\n // When the server was last heard from before the last outage (see lastHeardFrom).\r\n let lastHeardBeforeOutage = 0;\r\n\r\n let disconnected = false;\r\n\r\n const hasOwn = (id: string) => models.value.some(m => m.id === id && ownModels.has(m));\r\n const onChannel = ownOnChannel.get(name);\r\n if (onChannel) onChannel.add(hasOwn);\r\n else ownOnChannel.set(name, new Set([hasOwn]));\r\n\r\n const guard = (end: string) => {\r\n guardEnds.push(end);\r\n };\r\n const guards = guardsOnChannel.get(name);\r\n if (guards) guards.add(guard);\r\n else guardsOnChannel.set(name, new Set([guard]));\r\n\r\n // Asks the server for an own model by id; the answer arrives through onUpdate or onDelete.\r\n // `again: true`, for a model already answered for, makes the server reply model::delete if another\r\n // client deleted it meanwhile, as a relayed delete may not have reached this client. Without it, the\r\n // server lifts that delete and answers a bare id. After MODEL_TOMBSTONE_SECONDS the server has\r\n // forgotten the delete, and this client sends the model again.\r\n const askFor = (colibri: Colibri, model: T) => {\r\n awaitingAnswer.set(model.id, colibri);\r\n colibri.sendMessage(\r\n name,\r\n 'model::request',\r\n answered.has(model) ? { id: model.id, again: true } : { id: model.id }\r\n );\r\n };\r\n\r\n // For an id deleted here a moment ago and registered again: the request lifts the server's delete,\r\n // and the full model follows at once since the server has nothing for the id. Until the bare-id\r\n // answer, updates for the id are dropped (see onUpdate): a remote update sent before the server\r\n // had the delete would be taken for the answer.\r\n const sendAfresh = (colibri: Colibri, model: T) => {\r\n colibri.sendMessage(name, 'model::request', { id: model.id });\r\n afresh.set(model.id, Date.now());\r\n answered.add(model);\r\n releaseHeldChanges(model);\r\n sendUpdate(colibri, model, toJsonOf(model));\r\n };\r\n\r\n // Requests every model once all own models have their answer, so that answer includes what was\r\n // sent in between (see guardFrom for what is sent after). Returns whether it asked.\r\n const catchUpOnceAnswered = () => {\r\n if (!catchingUpThrough || awaitingAnswer.size > 0) return false;\r\n const colibri = catchingUpThrough;\r\n catchingUpThrough = undefined;\r\n colibri.sendMessage(name, 'model::request');\r\n guardFrom(colibri);\r\n return true;\r\n };\r\n\r\n // Keeps the fields this client sends out of updates until the answer to a request sent now. An\r\n // earlier request is answered with what the server had when it read it, which may predate these\r\n // fields; applied, that undoes them on this client only, as the server does not relay an update\r\n // back to its sender. Every registration on the channel receives the answer, so all of them guard.\r\n const guardFrom = (colibri: Colibri) => {\r\n const end = askForEnd(colibri);\r\n for (const guardOnChannel of guardsOnChannel.get(name) ?? []) guardOnChannel(end);\r\n };\r\n\r\n const keepOut = (model: T, update: object) => {\r\n const end = guardEnds.at(-1);\r\n if (end === undefined) return;\r\n let kept = keptOut.get(model);\r\n if (!kept) keptOut.set(model, (kept = new Map<string, string>()));\r\n for (const key of Object.keys(update)) if (key !== 'id') kept.set(key, end);\r\n };\r\n\r\n const keptOutOf = (model: T) =>\r\n [...(keptOut.get(model) ?? [])].filter(([, end]) => guardEnds.includes(end)).map(([key]) => key);\r\n\r\n // The answer for `end` has come, and with it those for earlier requests; without `end`, none will come.\r\n const endGuard = (end?: string) => {\r\n guardEnds = end === undefined ? [] : guardEnds.slice(guardEnds.indexOf(end) + 1);\r\n if (guardEnds.length > 0) return;\r\n keptOut = new WeakMap<T, Map<string, string>>();\r\n answerMayFollow.clear();\r\n };\r\n\r\n // initial data fetch\r\n // Repeated after every reconnect, since remote updates relayed meanwhile are lost. Applied to the\r\n // model with the same id, so nothing is duplicated.\r\n //\r\n // The server forgets an app's models when its last client leaves and on restart, so own models are\r\n // asked for by id first (see askFor), and everything else once all are answered. Also on the first\r\n // fetch: the answer for every model carries an own model's id too and would be taken for its answer.\r\n withColibri(colibri => {\r\n catchingUpThrough = colibri;\r\n // Deferred so that models registered in the same block of code, or before `new Colibri()`, are asked for first.\r\n setTimeout(catchUpOnceAnswered, 0);\r\n onColibriDisconnected(colibri, () => {\r\n disconnected = true;\r\n // Answers to the requests on this connection will not come. Ended here, not on reconnect:\r\n // by then another registration on the channel may have sent the next one (see guardFrom).\r\n endGuard();\r\n // Not updated while the last reconnect's answers are pending: changes lost in the earlier\r\n // outage are still in question, so they are judged from then.\r\n if (askedAfterOutage.size === 0) lastHeardBeforeOutage = lastHeardFrom(colibri) ?? Date.now();\r\n // A delete sent after the last message from the server may have gone into a connection that was\r\n // already dead (Socket.IO loses what it sends until its ping timeout). Sent again, as colibri-unity\r\n // does; Socket.IO holds it until the reconnect. A delete that did arrive arrives twice, harmlessly.\r\n const since = (lastHeardFrom(colibri) ?? Date.now()) - 1000;\r\n for (const [id, at] of deletedHere) {\r\n if (at >= since) colibri.sendMessage(name, 'model::delete', { id });\r\n }\r\n });\r\n onColibriReconnected(colibri, () => {\r\n disconnected = false;\r\n // Requests from the previous connection will not be answered. Changes sent for models still\r\n // being confirmed may not have arrived, so they are held again and go out with the answer.\r\n awaitingAnswer.clear();\r\n deletedWhileAwaited.clear();\r\n askedAfterOutage.clear();\r\n afresh.clear();\r\n for (const id of [...confirming.keys()]) {\r\n const confirmation = endConfirmation(id);\r\n const model = models.value.find(m => m.id === id);\r\n if (confirmation && model && ownModels.has(model)) holdChanges(model, confirmation.props);\r\n }\r\n catchingUpThrough = colibri;\r\n for (const model of models.value) {\r\n if (!ownModels.has(model)) continue;\r\n if (answered.has(model)) askedAfterOutage.set(model.id, { shown: new Map(), lost: new Set() });\r\n askFor(colibri, model);\r\n }\r\n roundEnd = askedAfterOutage.size > 0 ? askForEnd(colibri) : undefined;\r\n catchUpOnceAnswered();\r\n });\r\n });\r\n\r\n // Register for updates\r\n RegisterChannel(name, (payload: Message) => {\r\n if (payload.command === 'model::update') {\r\n onUpdate((payload as ModelSyncMsg<T>).payload);\r\n } else if (payload.command === 'model::delete') {\r\n onDelete((payload as ModelSyncMsg<T>).payload.id);\r\n } else {\r\n console.error(`Unknown model command: ${payload.command}`);\r\n }\r\n });\r\n\r\n // Answers to askForEnd requests (see roundEnd, guardEnds, endWait). Every registration receives\r\n // each one sent on this connection and acts only on the ids it waits for.\r\n RegisterChannel(END_CHANNEL, (message: Message) => {\r\n if (message.command !== 'model::update') return;\r\n const id = (message.payload as { id?: unknown } | undefined)?.id;\r\n if (typeof id !== 'string') return;\r\n if (id === roundEnd) endRound();\r\n if (guardEnds.includes(id)) endGuard(id);\r\n const confirmed = [...confirming].find(([, confirmation]) => confirmation.end === id);\r\n if (confirmed) endWait(confirmed[0]);\r\n });\r\n\r\n // Handle updates\r\n const onUpdate = (modelData: Partial<T>) => {\r\n const model = models.value.find(m => m.id === modelData.id);\r\n const id = modelData.id;\r\n const afreshSince = id === undefined ? undefined : afresh.get(id);\r\n if (id !== undefined && afreshSince !== undefined) {\r\n // Until the bare-id answer, updates are ones sent before the server had the delete (see\r\n // sendAfresh).\r\n const waiting = Date.now() - afreshSince < AFRESH_ANSWER_MS;\r\n if (isBare(modelData) || !waiting) afresh.delete(id);\r\n if (isBare(modelData) || waiting) return;\r\n }\r\n if (id !== undefined && deletedWhileAwaited.delete(id) && !model && isBare(modelData)) return;\r\n const asker = id === undefined ? undefined : awaitingAnswer.get(id);\r\n if (id !== undefined && asker) {\r\n takeAnswer(id, asker, model, modelData);\r\n return;\r\n }\r\n\r\n // The answer to another registration on this channel that has an own model with this id. Not\r\n // listed: the full state it sends in reply is not relayed to this client, so the model would stay empty.\r\n if (!model && id !== undefined && isBare(modelData) && ownElsewhere(id)) return;\r\n applyUpdate(model, modelData);\r\n };\r\n\r\n const ownElsewhere = (id: string) => [...(ownOnChannel.get(name) ?? [])].some(has => has !== hasOwn && has(id));\r\n\r\n // The first update for an own model after its request settles it. With fields, the server has the\r\n // model (it stores an update before relaying it): applied as usual, except fields this client\r\n // changed while waiting, which are sent on top. A bare id means the server has nothing: send the\r\n // whole model. No model any more (deleted meanwhile): nothing to send.\r\n //\r\n // That update need not be the answer: the answer to the request for every model carries the id\r\n // too, and a remote update can arrive first. The real answer, made before the server had the\r\n // changes sent meanwhile, would then revert them on this client only. So changes that replace\r\n // server values are followed by another request for the model and an askForEnd request; later\r\n // changes are held and the sent fields kept out of updates until its answer (see endWait).\r\n // Otherwise guardFrom keeps the fields sent later out of updates until one more answer.\r\n //\r\n // After a reconnect the answer may also show a change sent before the outage that never arrived\r\n // (see lostChanges), and is again indistinguishable from a remote update. One more request follows\r\n // (see roundEnd), updates before its answer are checked, then the model is settled (see endRound).\r\n const takeAnswer = (id: string, asker: Colibri, model: T | undefined, modelData: Partial<T>) => {\r\n if (model) answered.add(model);\r\n\r\n // detach() was called since the request: it takes updates like a non-own model and sends\r\n // nothing.\r\n if (model && !ownModels.has(model)) {\r\n askedAfterOutage.delete(id);\r\n awaitingAnswer.delete(id);\r\n endConfirmation(id);\r\n if (!isBare(modelData)) applyUpdate(model, modelData);\r\n catchUpOnceAnswered();\r\n return;\r\n }\r\n\r\n if (!model || isBare(modelData)) {\r\n askedAfterOutage.delete(id);\r\n awaitingAnswer.delete(id);\r\n endConfirmation(id);\r\n if (model) {\r\n heldChanges.delete(model);\r\n sendUpdate(asker, model, toJsonOf(model));\r\n } else if (!isBare(modelData)) {\r\n applyUpdate(model, modelData);\r\n }\r\n catchUpOnceAnswered();\r\n return;\r\n }\r\n\r\n const afterOutage = askedAfterOutage.get(id);\r\n if (afterOutage) {\r\n const held = [...(heldChanges.get(model) ?? [])];\r\n for (const key of lostChanges(model, modelData, held)) afterOutage.lost.add(key);\r\n for (const [key, value] of Object.entries(modelData)) afterOutage.shown.set(key, value);\r\n applyUpdate(model, withoutKeys(withoutChanges(modelData, model, held), afterOutage.lost));\r\n return;\r\n }\r\n\r\n const confirmation = confirming.get(id);\r\n if (confirmation) {\r\n for (const key of confirmation.sent.keys()) {\r\n if (key in modelData) confirmation.shown.set(key, modelData[key as keyof T]);\r\n }\r\n const held = [...(heldChanges.get(model) ?? [])];\r\n applyUpdate(model, withoutKeys(withoutChanges(modelData, model, held), confirmation.sent.keys()));\r\n return;\r\n }\r\n\r\n awaitingAnswer.delete(id);\r\n const held = releaseHeldChanges(model);\r\n applyUpdate(model, withoutChanges(modelData, model, held));\r\n // Deleted by a subscriber while the list emitted: nothing more to send.\r\n if (!models.value.includes(model)) {\r\n catchUpOnceAnswered();\r\n return;\r\n }\r\n if (sendOnTop(id, asker, model, modelData, held, [])) return;\r\n if (!catchUpOnceAnswered()) guardFrom(asker);\r\n // Whichever of the two calls ran sent a request whose answer comes after this model's.\r\n const end = guardEnds.at(-1);\r\n if (end !== undefined) answerMayFollow.set(id, end);\r\n };\r\n\r\n // The answers to the reconnect requests are over (see takeAnswer): settles each own model with\r\n // the values the updates showed.\r\n const endRound = () => {\r\n roundEnd = undefined;\r\n const asked = [...askedAfterOutage];\r\n askedAfterOutage.clear();\r\n for (const [id, { shown, lost }] of asked) {\r\n const asker = awaitingAnswer.get(id);\r\n const model = models.value.find(m => m.id === id);\r\n // Settled already, by the bare id or model::delete.\r\n if (!asker || !model) continue;\r\n\r\n awaitingAnswer.delete(id);\r\n // detach() was called since: it sends nothing.\r\n if (!ownModels.has(model)) continue;\r\n const held = releaseHeldChanges(model);\r\n // Remember what the server still holds for the lost fields, stamped now: should the value sent\r\n // again below be lost too, the next outage's answer is judged against it, however long ago the\r\n // change was made.\r\n if (lost.size > 0)\r\n remember(model, Object.fromEntries([...lost].map(key => [key, shown.get(key)])) as Partial<T>);\r\n sendOnTop(id, asker, model, Object.fromEntries(shown) as Partial<T>, held, [...lost]);\r\n }\r\n catchUpOnceAnswered();\r\n };\r\n\r\n // Sends the held changes (property names) and the lost fields (see lostChanges) with their current\r\n // values, on top of what the server showed (`shown`). True if that replaces server values, in which\r\n // case the model is asked for again (see takeAnswer).\r\n const sendOnTop = (id: string, asker: Colibri, model: T, shown: Partial<T>, held: string[], lost: string[]) => {\r\n if (held.length === 0 && lost.length === 0) return false;\r\n // toJson() with no properties named is all of them.\r\n const current = toJsonOf(model) as Record<string, unknown>;\r\n const sent: Record<string, unknown> = held.length > 0 ? toJsonOf(model, held) : { id };\r\n for (const key of lost) sent[key] = current[key];\r\n sendUpdate(asker, model, sent as Partial<T>);\r\n\r\n // An undefined field is not sent, so no update shows it.\r\n const differs = new Map<string, string>();\r\n for (const [key, value] of Object.entries(sent)) {\r\n const json = JSON.stringify(value) as string | undefined;\r\n if (key === 'id' || json === undefined) continue;\r\n if (!(key in shown) || JSON.stringify(shown[key as keyof T]) !== json) differs.set(key, json);\r\n }\r\n if (differs.size === 0) return false;\r\n const replaced = new Map<string, string | undefined>();\r\n for (const key of differs.keys()) {\r\n replaced.set(key, key in shown ? JSON.stringify(shown[key as keyof T]) : undefined);\r\n }\r\n askFor(asker, model);\r\n confirming.set(id, { sent: differs, replaced, props: held, shown: new Map(), end: askForEnd(asker) });\r\n return true;\r\n };\r\n\r\n // Every answer made before the server had the sent fields has come (see takeAnswer), so the last\r\n // update that showed a field has what the server holds. The value sent: it arrived. Another value:\r\n // another client set it since, applied. The value it replaced, or none: it has not arrived (the\r\n // server holds back updates over CLIENT_MESSAGE_RATE_LIMIT, but never requests) or another client\r\n // set it back; sent again.\r\n //\r\n // The changes made meanwhile then go out without asking again: nothing stale can come any more.\r\n const endWait = (id: string) => {\r\n const confirmation = endConfirmation(id);\r\n const asker = awaitingAnswer.get(id);\r\n const model = models.value.find(m => m.id === id);\r\n // Settled already, by the bare id or model::delete.\r\n if (!confirmation || !asker || !model) return;\r\n\r\n awaitingAnswer.delete(id);\r\n const held = releaseHeldChanges(model);\r\n const theirs: Record<string, unknown> = { id };\r\n const again: string[] = [];\r\n for (const [key, json] of confirmation.sent) {\r\n const shown = confirmation.shown.has(key)\r\n ? (JSON.stringify(confirmation.shown.get(key)) as string | undefined)\r\n : undefined;\r\n if (shown === json) continue;\r\n if (shown === undefined || shown === confirmation.replaced.get(key)) again.push(key);\r\n else theirs[key] = confirmation.shown.get(key);\r\n }\r\n applyUpdate(model, withoutChanges(theirs as Partial<T>, model, held));\r\n\r\n const current = toJsonOf(model) as Record<string, unknown>;\r\n const update: Record<string, unknown> = held.length > 0 ? toJsonOf(model, held) : { id };\r\n for (const key of again) update[key] = current[key];\r\n // detach() was called since: it sends nothing.\r\n if (ownModels.has(model) && Object.keys(update).length > 1) sendUpdate(asker, model, update as Partial<T>);\r\n catchUpOnceAnswered();\r\n };\r\n\r\n // Sends an update for an own model and records it for lostChanges and guardFrom.\r\n const sendUpdate = (colibri: Colibri, model: T, update: Partial<T>) => {\r\n colibri.sendMessage(name, 'model::update', update);\r\n remember(model, update, lastHeardFrom(colibri) ?? Number.POSITIVE_INFINITY);\r\n keepOut(model, update);\r\n };\r\n\r\n // Records `fields` in knownValues: as sent by this client if `heardAt` (when it last heard from the\r\n // server) is given, otherwise as shown by the server, which replaces the history. Undefined fields\r\n // are not sent at all, so they are skipped.\r\n const remember = (model: T, fields: Partial<T>, heardAt?: number) => {\r\n let known = knownValues.get(model);\r\n if (!known) knownValues.set(model, (known = new Map<string, KnownValue[]>()));\r\n const at = Date.now();\r\n for (const [key, field] of Object.entries(fields)) {\r\n const json = JSON.stringify(field) as string | undefined;\r\n if (key === 'id' || json === undefined) continue;\r\n const value = { json, at, sent: heardAt !== undefined };\r\n known.set(key, heardAt === undefined ? [value] : keepKnown([...(known.get(key) ?? []), value], heardAt));\r\n }\r\n };\r\n\r\n // Trims a field's values (oldest first) to the latest KNOWN_VALUES_KEPT up to `heardAt`, the first\r\n // KNOWN_VALUES_KEPT after it, and the newest. The server's latency probe comes every 100 ms, so the\r\n // connection died about when `heardAt` was: the last value that reached the server was sent around\r\n // then, and later ones went into the dead link, where only the newest counts.\r\n const keepKnown = (values: KnownValue[], heardAt: number): KnownValue[] => {\r\n const split = values.findIndex(v => v.at > heardAt);\r\n if (split < 0) return values.slice(-KNOWN_VALUES_KEPT);\r\n const after = values.slice(split);\r\n const newest = after.slice(KNOWN_VALUES_KEPT).slice(-1);\r\n return [...values.slice(0, split).slice(-KNOWN_VALUES_KEPT), ...after.slice(0, KNOWN_VALUES_KEPT), ...newest];\r\n };\r\n\r\n // Fields of an own model that an update in the reconnect round shows with a value from before this\r\n // client's last change: that change never reached the server (Socket.IO loses what is sent into a\r\n // dead connection until its ping timeout). Applied, the update would revert it on this client only,\r\n // so the value is kept and sent again.\r\n //\r\n // Any other value is applied: one the field never had is another client's, and so is one shown again\r\n // after this client's last change. An earlier value counts only if it was still current\r\n // KNOWN_VALUES_MS before the outage, counted from lastHeardBeforeOutage since Socket.IO notices the\r\n // outage late: a change that replaced it earlier arrived for certain.\r\n const lostChanges = (model: T, modelData: Partial<T>, held: string[]): string[] => {\r\n const known = knownValues.get(model);\r\n if (!known) return [];\r\n const since = lastHeardBeforeOutage - KNOWN_VALUES_MS;\r\n const heldKeys = new Set(held.length > 0 ? Object.keys(toJsonOf(model, held)) : []);\r\n const current = toJsonOf(model) as Record<string, unknown>;\r\n return Object.entries(modelData)\r\n .filter(([key, value]) => {\r\n const values = known.get(key) ?? [];\r\n const last = values.at(-1);\r\n if (!last?.sent || heldKeys.has(key) || !(key in current)) return false;\r\n const json = JSON.stringify(value) as string | undefined;\r\n const earlier = values.filter((_, i) => i + 1 < values.length && values[i + 1].at >= since);\r\n return (\r\n json !== last.json &&\r\n earlier.some(v => v.json === json) &&\r\n // Nothing to send when this client has what the server has anyway.\r\n JSON.stringify(current[key]) !== json\r\n );\r\n })\r\n .map(([key]) => key);\r\n };\r\n\r\n const withoutKeys = (modelData: Partial<T>, keys: Iterable<string>): Partial<T> => {\r\n const drop = new Set(keys);\r\n return Object.fromEntries(Object.entries(modelData).filter(([key]) => !drop.has(key))) as Partial<T>;\r\n };\r\n\r\n // Fields with a change SyncModel has not reported yet keep their value: the update predates the\r\n // change, and applying it would make the report send the update's value instead.\r\n const applyUpdate = (model: T | undefined, modelData: Partial<T>) => {\r\n if (model) {\r\n const unsent = [...(changesToSend.get(model) ?? [])];\r\n const update = withoutKeys(withoutChanges(modelData, model, unsent), keptOutOf(model));\r\n if (ownModels.has(model)) remember(model, update);\r\n\r\n // Update existing model\r\n updateOf(model, update);\r\n models.next([...models.value]);\r\n } else if (modelData.id) {\r\n if (recentlyDeletedHere(modelData.id)) return;\r\n const newModel = new registration.type(modelData.id);\r\n newModel.deletions.subscribe(() => {\r\n deleteHere(newModel);\r\n });\r\n\r\n newModel.modelChanges.subscribe(change => {\r\n noteChangeToSend(newModel, change);\r\n });\r\n newModel.modelChanges$.subscribe(changes => {\r\n changesToSend.delete(newModel);\r\n const update = toJsonOf(newModel, changes);\r\n SendMessage(name, 'model::update', update);\r\n keepOut(newModel, update);\r\n models.next([...models.value]);\r\n });\r\n\r\n updateOf(newModel, modelData);\r\n models.next([...models.value, newModel]);\r\n }\r\n };\r\n\r\n const holdChanges = (model: T, changes: string[]) => {\r\n const held = heldChanges.get(model);\r\n if (held) for (const change of changes) held.add(change);\r\n else heldChanges.set(model, new Set(changes));\r\n };\r\n\r\n const releaseHeldChanges = (model: T): string[] => {\r\n const held = [...(heldChanges.get(model) ?? [])];\r\n heldChanges.delete(model);\r\n return held;\r\n };\r\n\r\n // `modelData` without the fields that the property names in `changes` are sent as.\r\n const withoutChanges = (modelData: Partial<T>, model: T, changes: string[]): Partial<T> => {\r\n if (changes.length === 0) return modelData;\r\n const changed = new Set(Object.keys(toJsonOf(model, changes)).filter(key => key !== 'id'));\r\n return Object.fromEntries(Object.entries(modelData).filter(([key]) => !changed.has(key))) as Partial<T>;\r\n };\r\n\r\n const onDelete = (id: string) => {\r\n const model = models.value.find(m => m.id === id);\r\n detachOf(model);\r\n forget(id, model);\r\n };\r\n\r\n // Deletes a model here as for a remote delete, then on the server. Ignored unless it is the\r\n // instance listed for its id: a model replaced by registerModel no longer stands for it.\r\n const deleteHere = (model: T) => {\r\n if (models.value.find(m => m.id === model.id) !== model) return;\r\n rememberDeletedHere(model.id);\r\n forget(model.id, model);\r\n withColibri(colibri => {\r\n colibri.sendMessage(name, 'model::delete', { id: model.id });\r\n });\r\n };\r\n\r\n // Takes a deleted model out of the list and out of everything that waits for it.\r\n const forget = (id: string, model: T | undefined) => {\r\n if (model) heldChanges.delete(model);\r\n askedAfterOutage.delete(id);\r\n models.next(models.value.filter(m => m.id !== id));\r\n\r\n // model::delete can answer a request for an own model (another client deleted it during an\r\n // outage). That settles the model like an update, so catching up must not keep waiting for it.\r\n endConfirmation(id);\r\n const answerEnd = answerMayFollow.get(id);\r\n answerMayFollow.delete(id);\r\n if (awaitingAnswer.delete(id)) {\r\n deletedWhileAwaited.add(id);\r\n catchUpOnceAnswered();\r\n } else if (answerEnd !== undefined && guardEnds.includes(answerEnd)) {\r\n // Settled by an update that need not have been the answer, which may still come.\r\n deletedWhileAwaited.add(id);\r\n }\r\n };\r\n\r\n // The server may already have a model with this id (created by another client, or by this one before\r\n // a reload). So the model is asked for by id first, as Unity's SyncBehaviour does (see askFor): what\r\n // the server has wins, and the full model is sent only if it has nothing.\r\n //\r\n // If the id is already listed, from a remote update or an earlier instance, the new instance replaces\r\n // it in place and the listed one is detached. Registering the listed instance does nothing.\r\n const registerModel = (model: T) => {\r\n const listed = models.value.find(m => m.id === model.id);\r\n if (listed === model) return;\r\n if (listed && ownModels.has(listed)) {\r\n console.warn(\r\n `Colibri: registerModel was given a second model with the id '${model.id}' on channel ` +\r\n `'${name}'. It replaces the first one, which no longer syncs.`\r\n );\r\n }\r\n\r\n // Hold-back is decided when the change is made, not when SyncModel reports it 1 ms later: an answer\r\n // arriving in between would be applied over it.\r\n //\r\n // Held until the server has answered for the model, then sent on top of what it has: changes made\r\n // before `new Colibri()`, or after a reconnect but before the answer, would otherwise be undone by it.\r\n // Also changes made while disconnected: Socket.IO sends them on reconnect ahead of the model's\r\n // request, and to a server that forgot the model they would become all of it.\r\n const mustHold = () => !Colibri.getInstance(false) || disconnected || awaitingAnswer.has(model.id);\r\n model.modelChanges.subscribe(change => {\r\n if (mustHold()) holdChanges(model, [change]);\r\n else noteChangeToSend(model, change);\r\n });\r\n model.modelChanges$.subscribe({\r\n next: () => {\r\n const changes = [...(changesToSend.get(model) ?? [])];\r\n changesToSend.delete(model);\r\n if (changes.length > 0) {\r\n const colibri = Colibri.getInstance(false);\r\n if (!colibri || mustHold()) holdChanges(model, changes);\r\n else sendUpdate(colibri, model, toJsonOf(model, changes));\r\n }\r\n\r\n // Emit for own models too, held back or sent: the model has changed here, and a page rendering the\r\n // list has to show it.\r\n if (models.value.includes(model)) models.next([...models.value]);\r\n },\r\n // delete() and detach() end the stream: nothing more is sent for the model, neither held changes\r\n // nor the whole model after a reconnect.\r\n complete: () => {\r\n ownModels.delete(model);\r\n heldChanges.delete(model);\r\n changesToSend.delete(model);\r\n }\r\n });\r\n\r\n model.deletions.subscribe(() => {\r\n deleteHere(model);\r\n });\r\n const reused = recentlyDeletedHere(model.id);\r\n deletedHere.delete(model.id);\r\n ownModels.add(model);\r\n if (listed) {\r\n endConfirmation(model.id);\r\n askedAfterOutage.delete(model.id);\r\n detachOf(listed);\r\n models.next(models.value.map(m => (m === listed ? model : m)));\r\n } else {\r\n models.next([...models.value, model]);\r\n }\r\n\r\n // While disconnected, the reconnect asks for it.\r\n withColibri(colibri => {\r\n if (disconnected || !ownModels.has(model)) return;\r\n if (reused) sendAfresh(colibri, model);\r\n else askFor(colibri, model);\r\n });\r\n };\r\n\r\n return [models.asObservable(), registerModel];\r\n};\r\n","import { Subject, auditTime, buffer, filter, map, share } from 'rxjs';\r\n\r\nexport abstract class SyncModel<T> {\r\n // Non-enumerable (stays out of Object.keys() and JSON), defined per instance by registerSyncedProperty().\r\n // `declare` emits no field initializer.\r\n declare private __syncedProperties?: Record<string, string | symbol>;\r\n\r\n public readonly id: string;\r\n\r\n // True while update() applies a remote update, so the setters do not echo it.\r\n public applyingRemoteUpdate = false;\r\n\r\n public readonly modelChanges = new Subject<string>();\r\n public readonly modelChanges$ = this.modelChanges.pipe(\r\n // auditTime arms its timer on the first change only; bufferTime keeps an interval running while idle.\r\n buffer(this.modelChanges.pipe(auditTime(1))),\r\n map(changes => changes.filter(this.uniq)),\r\n filter(changes => changes.length > 0),\r\n share()\r\n );\r\n\r\n // Emits once on delete(), which RegisterModelSync sends on as model::delete. Public only for RegisterModelSync.\r\n public readonly deletions = new Subject<void>();\r\n // ES private: a subclass may have a `deleted` of its own.\r\n #deleted = false;\r\n\r\n public constructor(id: string) {\r\n this.id = id;\r\n }\r\n\r\n private uniq(this: void, value: string, index: number, array: string[]) {\r\n return array.indexOf(value) === index;\r\n }\r\n\r\n protected onModelChanges(prop: string): void {\r\n this.modelChanges.next(prop);\r\n }\r\n\r\n /**\r\n * Deletes the model for every client of the app (Unity destroys its GameObject) and removes it\r\n * from this client's list. The model stops syncing.\r\n */\r\n public delete(): void {\r\n if (this.#deleted) return;\r\n this.#deleted = true;\r\n if (!this.deletions.observed) {\r\n console.warn(\r\n `Colibri: delete() of '${this.id}', which no RegisterModelSync lists. Register it, or ` +\r\n 'take it from the list RegisterModelSync gives, to delete it for every client.'\r\n );\r\n }\r\n // Detach first: completing modelChanges flushes a buffered change, which must be sent before the delete.\r\n // SyncModel's own detach(), as a model class may define one too.\r\n SyncModel.prototype.detach.call(this);\r\n this.deletions.next();\r\n this.deletions.complete();\r\n }\r\n\r\n /**\r\n * Stops syncing the model on this client only; every other client keeps it. RegisterModelSync\r\n * calls it on a remote delete and when registerModel replaces a listed copy.\r\n */\r\n public detach(): void {\r\n this.modelChanges.complete();\r\n }\r\n\r\n public registerSyncedProperty(name: string, prop: string | symbol): void {\r\n // Called per instance: by @Synced's initializer for every member in the prototype chain, and by synced().\r\n let syncedProperties = this.__syncedProperties;\r\n if (syncedProperties === undefined) {\r\n syncedProperties = {};\r\n Object.defineProperty(this, '__syncedProperties', {\r\n value: syncedProperties,\r\n enumerable: false,\r\n writable: true,\r\n configurable: true\r\n });\r\n }\r\n\r\n syncedProperties[name] = prop;\r\n }\r\n\r\n private getSyncedProperties(): Record<string, string | symbol> {\r\n return this.__syncedProperties ?? {};\r\n }\r\n\r\n public update(updates: Partial<T>): void {\r\n const syncedProps = this.getSyncedProperties();\r\n\r\n this.applyingRemoteUpdate = true;\r\n try {\r\n for (const key of Object.keys(updates)) {\r\n if (key !== 'id') {\r\n const localKey = syncedProps[key];\r\n if (localKey) {\r\n // localKey is only known at runtime, so its type can't be tied to updates[key].\r\n (this as unknown as Record<string | symbol, unknown>)[localKey] = updates[key as keyof T];\r\n } else {\r\n console.warn(`Unknown property ${key} in ${this.constructor.name}`);\r\n }\r\n }\r\n }\r\n } finally {\r\n this.applyingRemoteUpdate = false;\r\n }\r\n }\r\n\r\n public toJson(attributes: string[] = []): Partial<T> {\r\n const json: Record<string, unknown> = { id: this.id };\r\n\r\n const syncedProps = this.getSyncedProperties();\r\n for (const key of Object.keys(syncedProps)) {\r\n const localKey = syncedProps[key] as keyof SyncModel<T>;\r\n if (attributes.length === 0 || attributes.includes(localKey)) {\r\n // Always a synced data field, never a method, but keyof SyncModel<T> can't express that.\r\n // eslint-disable-next-line @typescript-eslint/unbound-method\r\n json[key] = this[localKey];\r\n }\r\n }\r\n\r\n return json as Partial<T>;\r\n }\r\n}\r\n","import { SyncModel } from './SyncModel';\r\n\r\n/**\r\n * This decorator can be attached to any `accessor` member of a SyncModel.\r\n * It will cause the property to be synchronized with the server.\r\n * Refer to https://github.com/tc39/proposal-decorators\r\n */\r\nexport function Synced<This extends SyncModel<unknown>, V>(syncedName: string = '') {\r\n return function (\r\n value: ClassAccessorDecoratorTarget<This, V>,\r\n context: ClassAccessorDecoratorContext<This, V>\r\n ): ClassAccessorDecoratorResult<This, V> {\r\n // Unreachable for typed callers; plain-JS/Babel callers are not covered by the decorator types.\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n if (context.kind !== 'accessor') {\r\n throw new Error('@Synced() must decorate an `accessor` member');\r\n }\r\n\r\n const key = String(context.name);\r\n const name = (syncedName || key).toLowerCase();\r\n\r\n // Per instance, after the accessor's own initializer.\r\n context.addInitializer(function (this: This) {\r\n this.registerSyncedProperty(name, key);\r\n });\r\n\r\n return {\r\n get(this: This) {\r\n return value.get.call(this);\r\n },\r\n set(this: This, newVal: V) {\r\n value.set.call(this, newVal);\r\n\r\n // No echo of a remote update (SyncModel.update()).\r\n if (!this.applyingRemoteUpdate) {\r\n this.modelChanges.next(key);\r\n }\r\n }\r\n };\r\n };\r\n}\r\n\r\n/**\r\n * Declares synchronized properties without a decorator, for plain JavaScript. Call it in the\r\n * constructor of a SyncModel subclass, after `super(id)`, with each property and its start value:\r\n *\r\n * ```js\r\n * class Cube extends SyncModel {\r\n * constructor(id) {\r\n * super(id);\r\n * synced(this, { x: 0, y: 0, color: 'red' });\r\n * }\r\n * }\r\n * ```\r\n *\r\n * Each property syncs as with `@Synced()`, under its lowercased name or under `names[property]`.\r\n * Throws for a property the model already has as a member, and for a start value of `undefined`.\r\n */\r\nexport function synced<M extends SyncModel<unknown>, P extends Record<string, unknown>>(\r\n model: M,\r\n values: P,\r\n names: Partial<Record<keyof P & string, string>> = {}\r\n): asserts model is M & P {\r\n for (const [key, initial] of Object.entries(values)) {\r\n if (initial === undefined) {\r\n throw new Error(`synced(): '${key}' has no start value. undefined is not JSON: use null for none.`);\r\n }\r\n if (key in model) {\r\n throw new Error(\r\n `synced(): '${key}' is already a member of ${model.constructor.name}, a synced property or one of ` +\r\n \"SyncModel's. Give it another name.\"\r\n );\r\n }\r\n\r\n let value: unknown = initial;\r\n model.registerSyncedProperty((names[key as keyof P & string] || key).toLowerCase(), key);\r\n Object.defineProperty(model, key, {\r\n enumerable: true,\r\n get: () => value,\r\n set: (newValue: unknown) => {\r\n value = newValue;\r\n // No echo of a remote update (SyncModel.update()).\r\n if (!model.applyingRemoteUpdate) model.modelChanges.next(key);\r\n }\r\n });\r\n }\r\n}\r\n","import type { Observable } from 'rxjs';\r\nimport { RegisterModelSync } from './ModelSynchronization';\r\nimport { SyncModel } from './SyncModel';\r\nimport { Synced } from './Synced';\r\n\r\n/** `[x, y, z]`, as colibri-unity sends a Vector3. */\r\nexport type Vector3 = [number, number, number];\r\n\r\n/** `[x, y, z, w]`, as colibri-unity sends a Quaternion. */\r\nexport type Quaternion = [number, number, number, number];\r\n\r\n/**\r\n * The web side of colibri-unity's `SyncTransform`: active state, position, rotation and scale in\r\n * Unity's coordinates. Register it with {@link RegisterSyncTransforms}.\r\n *\r\n * Members mirror colibri-unity's `[Sync]` members. A Unity object with a member's Sync box unticked\r\n * never sends it, so it keeps its default here, which equals Unity's placeholder.\r\n *\r\n * Assign a new array to change a vector: `transform.position = [1, 2, 3]` is sent,\r\n * `transform.position[0] = 1` is not.\r\n */\r\nexport class SyncTransform extends SyncModel<SyncTransform> {\r\n /** Unity's `activeSelf`. `false` hides the object on the Unity clients. */\r\n @Synced()\r\n accessor active = true;\r\n\r\n /** World position, or local position with *Use Local Transform* ticked. */\r\n @Synced()\r\n accessor position: Vector3 = [0, 0, 0];\r\n\r\n /** World rotation, or local rotation with *Use Local Transform* ticked. */\r\n @Synced()\r\n accessor rotation: Quaternion = [0, 0, 0, 1];\r\n\r\n /** Local scale. */\r\n @Synced()\r\n accessor scale: Vector3 = [1, 1, 1];\r\n\r\n /**\r\n * Id of the Unity client that simulates the object's physics (`PhysicsAuthority`). Declared so a\r\n * remote update carrying it is not warned about as an unknown property. Setting it takes the\r\n * authority away from that client.\r\n */\r\n @Synced()\r\n accessor physicsId = '';\r\n}\r\n\r\n/**\r\n * {@link RegisterModelSync} for `SyncTransform`: returns an Observable of all of them, which emits\r\n * when one is added, changed or deleted, and a function that registers a new one.\r\n *\r\n * Unity objects with a `ModelId` sync on a channel of their own, so pass the same `modelId`. Call\r\n * this once per channel and share the result: two registrations on one channel in one page do not\r\n * receive each other's updates.\r\n */\r\nexport const RegisterSyncTransforms = (\r\n modelId = ''\r\n): [Observable<SyncTransform[]>, (transform: SyncTransform) => void] =>\r\n RegisterModelSync<SyncTransform>({\r\n name: modelId ? `synctransform_${modelId}` : 'synctransform',\r\n type: SyncTransform\r\n });\r\n"],"mappings":"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","names":["Subject","held"]}
|
package/package.json
CHANGED
|
@@ -1,43 +1,105 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@hcikn/colibri",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Communication Library for Research Prototyping - Allows object synchronization, networking, and voice chat with little to no code required",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"devEngines": {
|
|
25
|
+
"runtime": {
|
|
26
|
+
"name": "node",
|
|
27
|
+
"version": ">=22",
|
|
28
|
+
"onFail": "warn"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"lint": "eslint .",
|
|
34
|
+
"lint:fix": "eslint . --fix",
|
|
35
|
+
"format": "prettier --write .",
|
|
36
|
+
"format:check": "prettier --check .",
|
|
37
|
+
"prepare": "cd .. && husky colibri-web/.husky",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"release": "npm run build && npm publish --access public",
|
|
40
|
+
"samples/broadcast": "tsx samples/broadcast.ts",
|
|
41
|
+
"samples/model-sync": "tsx samples/model-sync.ts",
|
|
42
|
+
"samples/remote-logging": "tsx samples/remote-logging.ts",
|
|
43
|
+
"samples/rest-api": "tsx samples/rest-api.ts",
|
|
44
|
+
"samples/sync-transform": "tsx samples/sync-transform.ts",
|
|
45
|
+
"samples/verification-peer": "tsx samples/verification-peer.ts",
|
|
46
|
+
"samples/webxr": "npm run build && vite --config samples/webxr/vite.config.ts",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:coverage": "vitest run --coverage",
|
|
50
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
51
|
+
"test:e2e:watch": "vitest --config vitest.e2e.config.ts",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
},
|
|
54
|
+
"author": "HCI Group, University of Konstanz",
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"homepage": "https://github.com/hcigroupkonstanz/colibri",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/hcigroupkonstanz/colibri.git",
|
|
60
|
+
"directory": "colibri-web"
|
|
61
|
+
},
|
|
62
|
+
"files": [
|
|
63
|
+
"/dist"
|
|
64
|
+
],
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"rxjs": "^7.8.1"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"socket.io-client": "^4.7.1",
|
|
73
|
+
"ts-custom-error": "^3.3.1"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@eslint/js": "^10.0.1",
|
|
77
|
+
"@swc/core": "^1.3.96",
|
|
78
|
+
"@types/lodash": "^4.14.195",
|
|
79
|
+
"@types/node": "^20.19.39",
|
|
80
|
+
"@types/three": "^0.186.0",
|
|
81
|
+
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
|
82
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
83
|
+
"eslint": "^10.7.0",
|
|
84
|
+
"eslint-config-prettier": "^10.1.8",
|
|
85
|
+
"eslint-plugin-n": "^18.2.2",
|
|
86
|
+
"eslint-plugin-promise": "^7.3.0",
|
|
87
|
+
"husky": "^9.1.7",
|
|
88
|
+
"lint-staged": "^15.5.2",
|
|
89
|
+
"prettier": "^3.9.6",
|
|
90
|
+
"rxjs": "^7.8.1",
|
|
91
|
+
"three": "^0.186.1",
|
|
92
|
+
"tsup": "^8.5.0",
|
|
93
|
+
"tsx": "^4.21.0",
|
|
94
|
+
"typescript": "^5.1.6",
|
|
95
|
+
"typescript-eslint": "^8.65.0",
|
|
96
|
+
"vite": "^8.3.5",
|
|
97
|
+
"vitest": "^3.2.4"
|
|
98
|
+
},
|
|
99
|
+
"lint-staged": {
|
|
100
|
+
"**/*.{ts,js}": [
|
|
101
|
+
"eslint --fix",
|
|
102
|
+
"prettier --write"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
}
|