@univerjs-pro/collaboration-endpoint 1.0.0-insiders.20260808-70eefdd → 1.0.0

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/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # @univerjs-pro/collaboration-endpoint
2
+
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ `UniverCollabEndpoint` is the protocol layer of a Univer Collaboration server. It receives HTTP requests and WebSocket messages dispatched by Node Transport, uses the `UniverCollabService` API to read and write collaboration data, and manages Sessions, Unit rooms, Presence, ACKs, and broadcasts.
6
+
7
+ ```text
8
+ Node HTTP server
9
+ → Transport application authentication; sets ctx.userID and ctx.customData
10
+ → UniverCollabEndpoint HTTP/WebSocket protocol, Sessions, and realtime rooms
11
+ → UniverCollabService data reads, changeset submission, and Unit lifecycle
12
+ → Database Adapter snapshot, changeset, and revision persistence
13
+ ```
14
+
15
+ Register Endpoint with `@univerjs-pro/collaboration-transport-node` through `transport.register()`. It does not start a separate HTTP or WebSocket server.
16
+
17
+ ## Server assembly
18
+
19
+ ```bash
20
+ pnpm add \
21
+ @univerjs-pro/collaboration-service \
22
+ @univerjs-pro/collaboration-endpoint \
23
+ @univerjs-pro/collaboration-transport-node \
24
+ @univerjs-pro/collaboration-database-memory
25
+ ```
26
+
27
+ ```ts
28
+ import { MemoryDatabaseAdapter } from '@univerjs-pro/collaboration-database-memory';
29
+ import { UniverCollabEndpoint } from '@univerjs-pro/collaboration-endpoint';
30
+ import { UniverCollabService } from '@univerjs-pro/collaboration-service';
31
+ import { createNodeTransport } from '@univerjs-pro/collaboration-transport-node';
32
+
33
+ const database = new MemoryDatabaseAdapter();
34
+ const collabService = new UniverCollabService({ dbAdapter: database });
35
+ const endpoint = new UniverCollabEndpoint(collabService);
36
+ const transport = createNodeTransport();
37
+
38
+ // Authenticate HTTP requests and attach identity in Transport middleware first.
39
+ transport.use(async (ctx, next) => {
40
+ const user = await authenticate(ctx.incomingMessage);
41
+ if (!user) {
42
+ ctx.response.statusCode = 401;
43
+ ctx.response.end('Authentication required');
44
+ return;
45
+ }
46
+
47
+ ctx.userID = user.userId;
48
+ ctx.customData.user = user;
49
+ await next();
50
+ });
51
+
52
+ // Endpoint must be registered after authentication middleware.
53
+ transport.register(endpoint);
54
+ ```
55
+
56
+ This uses the Memory Adapter to keep the example minimal; all data is lost when the process exits. See the `@univerjs-pro/collaboration-transport-node` README for attaching Node HTTP requests and WebSocket upgrades.
57
+
58
+ All Collaboration and Univer frontend packages should use matching versions from the same release cohort.
59
+
60
+ ## Two request classes
61
+
62
+ Endpoint handles two broad protocol classes: document-data requests and realtime Sessions. They use different middleware and contexts.
63
+
64
+ ### Document-data requests
65
+
66
+ Snapshot, block, fetch-missing, new-changes, and Unit delete/recover are HTTP requests. They use the identity and custom data attached to the current HTTP request by Transport middleware:
67
+
68
+ ```text
69
+ HTTP snapshot / block / fetch-missing / new-changes / delete / recover
70
+ → Transport middleware sets ctx.userID and ctx.customData
71
+ → Endpoint parses the protocol and calls UniverCollabService APIs
72
+ → Service middleware reads ctx.userID and ctx.customData
73
+ → Service / Database Adapter
74
+ ```
75
+
76
+ Service middleware receives `ctx.userID` and `ctx.customData` from the current HTTP request's Transport middleware. They can be used for authorization, logging, and external integrations. For example:
77
+
78
+ ```ts
79
+ import { CollabError } from '@univerjs-pro/collaboration-service';
80
+
81
+ collabService.use('readUnitData', async (ctx, next) => {
82
+ // Both fields come from Transport middleware for the current HTTP request.
83
+ if (!await acl.canRead(ctx.userID, ctx.request.unitID)) {
84
+ throw new CollabError('PERMISSION_DENIED', 'Unit is not accessible');
85
+ }
86
+
87
+ await next();
88
+ });
89
+ ```
90
+
91
+ `readUnitData` protects snapshot, block, and fetch-missing. New-changes and Unit delete/recover use the Service actions `submitChangeset`, `deleteUnits`, and `recoverUnits`, respectively.
92
+
93
+ ### Realtime Sessions
94
+
95
+ Session-ticket and WebSocket open, HELLO, HEARTBEAT, JOIN, LEAVE, and Presence belong to the realtime Session flow:
96
+
97
+ ```text
98
+ GET /universer-api/user/session-ticket
99
+ → Transport middleware sets ctx.userID and ctx.customData
100
+ → Endpoint stores the association server-side and returns a one-time opaque ticket
101
+ → WebSocket handshake consumes the ticket; invalid tickets return HTTP 401 before upgrade
102
+ → Endpoint creates Session { userID, memberID, customData }
103
+ → Endpoint middleware reads ctx.session
104
+ ```
105
+
106
+ The ticket string does not contain `userID` or `customData`. Of all HTTP routes, only the session-ticket request carries its current `ctx.userID` and `ctx.customData` into the WebSocket Session.
107
+
108
+ Endpoint middleware reads them from `ctx.session.userID` and `ctx.session.customData`. Endpoint generates a `memberID` for each WebSocket Session, and it changes after reconnecting.
109
+
110
+ ```ts
111
+ import { CollabError } from '@univerjs-pro/collaboration-service';
112
+
113
+ endpoint.use('joinUnit', async (ctx, next) => {
114
+ // Both fields originate from the session-ticket HTTP request.
115
+ if (!await acl.canRead(ctx.session.userID, ctx.unitID)) {
116
+ throw new CollabError('PERMISSION_DENIED', 'Unit is not accessible');
117
+ }
118
+
119
+ await next();
120
+ });
121
+ ```
122
+
123
+ ### `new-changes` connects both classes
124
+
125
+ New-changes is an HTTP document-data request, but its ACK and broadcast are delivered over WebSocket:
126
+
127
+ ```text
128
+ HTTP new-changes { memberID }
129
+ → Endpoint locates an online Session by memberID
130
+ → verifies session.userID === current HTTP ctx.userID
131
+ → verifies that the Session has JOINed the target Unit
132
+ → calls the Service with the current HTTP ctx.userID and ctx.customData
133
+ → sends ACK and broadcast over WebSocket
134
+ ```
135
+
136
+ `memberID` is only used to locate a WebSocket Session and is not an identity credential by itself. Endpoint owns these associations and checks; the application does not need to implement them.
137
+
138
+ ## Middleware
139
+
140
+ Endpoint provides four realtime protocol middleware actions. Document-data requests use the Service middleware described above; see the `@univerjs-pro/collaboration-service` README for all Service actions.
141
+
142
+ | Action | When it runs | Visible fields | Retry semantics | Typical uses |
143
+ | --- | --- | --- | --- | --- |
144
+ | `connect` | After consuming the ticket and creating the Session, before handling `HELLO` | `session`, `connection`, `member` | Once per connection; runs again after reconnecting | Connection policy, member names and avatars |
145
+ | `joinUnit` | After receiving `JOIN`, before the Session joins the room | `session`, `unitID` | Once for the Session's first JOIN; Endpoint does not retry | Room read ACL |
146
+ | `receivePresence` | After a joined Session sends Presence, before broadcasting to the room | `session`, `unitID`, `payload`, `customData` | Once per Presence message; Endpoint does not retry | Presence validation, rate limits, filtering |
147
+ | `sendPresence` | Before sending Presence to one room member | Fields above plus `targetMemberID` | Once per target member; send failures are not retried | Per-recipient Presence filtering |
148
+
149
+ The `session` in this table originates from the session-ticket request. Top-level `customData` in a Presence context is a separate object created by Transport for the current WebSocket message.
150
+
151
+ Middleware for the same action runs in registration order. `await next()` enters the next middleware; throw `CollabError` for an expected rejection.
152
+
153
+ `joinUnit` only protects the realtime room and cannot replace Service `readUnitData` middleware. A client that has not joined a room can still issue a snapshot HTTP request directly.
154
+
155
+ ## Protocol route reference
156
+
157
+ The table uses the default `protocolBasePath=/universer-api`. Univer Collaboration Client normally calls these routes; applications do not need to wrap them manually.
158
+
159
+ | Channel | Method / Message | Path | Purpose |
160
+ | --- | --- | --- | --- |
161
+ | HTTP | `GET` | `/universer-api/user/session-ticket` | Obtain a one-time WebSocket ticket |
162
+ | HTTP | `GET` | `/universer-api/snapshot/:type/unit/:unitID/rev/:revision` | Load Unit snapshot and changesets |
163
+ | HTTP | `GET` | `/universer-api/snapshot/:type/unit/:unitID/block/:blockID` | Load a Unit block |
164
+ | HTTP | `GET` | `/universer-api/snapshot/block/:type/unit/:unitID/block/:blockID` | Load a parsed Unit block |
165
+ | HTTP | `GET` | `/universer-api/snapshot/:type/unit/:unitID/fetchmissing?from=:from&to=:to` | Load missing changesets |
166
+ | HTTP | `POST` | `/universer-api/comb/:type/unit/:unitID/new_changes` | Submit a changeset |
167
+ | HTTP | `DELETE` | `/universer-api/snapshot/-/units` | Delete Units |
168
+ | HTTP | `POST` | `/universer-api/snapshot/-/units/recover` | Recover Units |
169
+ | WebSocket open | `GET` + Upgrade | `/universer-api/comb/connect?sessionTicket=:ticket` | Consume a ticket and establish a Session |
170
+ | WebSocket | `HELLO/HEARTBEAT/JOIN/LEAVE/Presence` | `/universer-api/comb/connect` | Members, rooms, and Presence |
171
+
172
+ ## Configuration and deployment
173
+
174
+ ```ts
175
+ const endpoint = new UniverCollabEndpoint(collabService, {
176
+ protocolBasePath: '/universer-api', // default
177
+ sessionTicketTtlMs: 5 * 60_000, // default: 5 minutes
178
+ });
179
+ ```
180
+
181
+ Sessions, Unit rooms, and pending-send queues are process-local, so realtime delivery is only guaranteed within a single Endpoint process. Cross-process online members, rooms, and broadcasts are not currently supported.
182
+
183
+ WebSocket ACKs and broadcasts are not guaranteed to reach the client and are not an authoritative data source. After disconnecting, clients use revisions and fetch-missing HTTP requests to recover confirmed changesets.
184
+
185
+ ### Shutdown
186
+
187
+ Transport disposes registered Endpoints. Endpoint clears Sessions, rooms, and Service event subscriptions. The application must still dispose the Service and Database Adapter.
188
+
189
+ ```ts
190
+ await transport.dispose();
191
+ await collabService.dispose();
192
+ await database.dispose();
193
+ ```
@@ -0,0 +1,198 @@
1
+ # @univerjs-pro/collaboration-endpoint
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md)
4
+
5
+ `UniverCollabEndpoint` 是 Univer Collaboration 服务端的协议层。它接收 Node Transport 分派的 HTTP 请求和 WebSocket 消息,使用 `UniverCollabService` API 读写协同数据,并管理 Session、Unit 房间、Presence、ACK 和广播。
6
+
7
+ ```text
8
+ Node HTTP server
9
+ → Transport 应用认证,挂载 ctx.userID 和 ctx.customData
10
+ → UniverCollabEndpoint HTTP/WebSocket 协议、Session 和实时房间
11
+ → UniverCollabService 数据读取、changeset 提交和 Unit 生命周期
12
+ → Database Adapter snapshot、changeset 和 revision 持久化
13
+ ```
14
+
15
+ Endpoint 通过 `transport.register()` 注册到 `@univerjs-pro/collaboration-transport-node`,
16
+ 不需要单独启动 HTTP 或 WebSocket server。
17
+
18
+ ## 服务端组装
19
+
20
+ ```bash
21
+ pnpm add \
22
+ @univerjs-pro/collaboration-service \
23
+ @univerjs-pro/collaboration-endpoint \
24
+ @univerjs-pro/collaboration-transport-node \
25
+ @univerjs-pro/collaboration-database-memory
26
+ ```
27
+
28
+ ```ts
29
+ import { MemoryDatabaseAdapter } from '@univerjs-pro/collaboration-database-memory';
30
+ import { UniverCollabEndpoint } from '@univerjs-pro/collaboration-endpoint';
31
+ import { UniverCollabService } from '@univerjs-pro/collaboration-service';
32
+ import { createNodeTransport } from '@univerjs-pro/collaboration-transport-node';
33
+
34
+ const database = new MemoryDatabaseAdapter();
35
+ const collabService = new UniverCollabService({ dbAdapter: database });
36
+ const endpoint = new UniverCollabEndpoint(collabService);
37
+ const transport = createNodeTransport();
38
+
39
+ // 应用先在 Transport middleware 中认证 HTTP 请求并挂载身份。
40
+ transport.use(async (ctx, next) => {
41
+ const user = await authenticate(ctx.incomingMessage);
42
+ if (!user) {
43
+ ctx.response.statusCode = 401;
44
+ ctx.response.end('Authentication required');
45
+ return;
46
+ }
47
+
48
+ ctx.userID = user.userId;
49
+ ctx.customData.user = user;
50
+ await next();
51
+ });
52
+
53
+ // Endpoint 必须注册在认证 middleware 之后。
54
+ transport.register(endpoint);
55
+ ```
56
+
57
+ 这里使用 Memory Adapter 保持示例最小,进程退出后数据会丢失。Node HTTP server 和 WebSocket upgrade 的挂载方式见 `@univerjs-pro/collaboration-transport-node` README。
58
+
59
+ 全部 Collaboration 与 Univer 前端 package 应使用同一 release cohort 的匹配版本。
60
+
61
+ ## 两类请求
62
+
63
+ Endpoint 处理的协议可以先分成两组:文档数据请求和实时 Session。两组请求使用不同的 middleware 和上下文。
64
+
65
+ ### 文档数据请求
66
+
67
+ snapshot、block、fetch-missing、new-changes 和 Unit delete/recover 都是 HTTP 请求。它们使用当前 HTTP 请求中由 Transport middleware 挂载的身份和自定义数据:
68
+
69
+ ```text
70
+ HTTP snapshot / block / fetch-missing / new-changes / delete / recover
71
+ → Transport middleware 设置 ctx.userID 和 ctx.customData
72
+ → Endpoint 解析协议并调用 UniverCollabService API
73
+ → Service middleware 通过 ctx.userID 和 ctx.customData 读取
74
+ → Service / Database Adapter
75
+ ```
76
+
77
+ Service middleware 获取的是当前 HTTP 请求中由 Transport middleware 挂载的 `ctx.userID` 和
78
+ `ctx.customData`,可用于权限控制、日志和外部系统集成等。下面以读取权限为例。
79
+
80
+ ```ts
81
+ import { CollabError } from '@univerjs-pro/collaboration-service';
82
+
83
+ collabService.use('readUnitData', async (ctx, next) => {
84
+ // ctx.userID 和 ctx.customData 来自当前 HTTP 请求的 Transport middleware。
85
+ if (!await acl.canRead(ctx.userID, ctx.request.unitID)) {
86
+ throw new CollabError('PERMISSION_DENIED', 'Unit is not accessible');
87
+ }
88
+
89
+ await next();
90
+ });
91
+ ```
92
+
93
+ `readUnitData` 保护 snapshot、block 和 fetch-missing。new-changes 和 Unit delete/recover 分别使用 Service 的 `submitChangeset`、`deleteUnits` 和 `recoverUnits` action。
94
+
95
+ ### 实时 Session
96
+
97
+ session-ticket 和 WebSocket open、HELLO、HEARTBEAT、JOIN、LEAVE、Presence 属于实时 Session 流程:
98
+
99
+ ```text
100
+ GET /universer-api/user/session-ticket
101
+ → Transport middleware 设置 ctx.userID 和 ctx.customData
102
+ → Endpoint 在服务端保存关联,返回一次性 opaque ticket
103
+ → WebSocket handshake 消费 ticket;无效 ticket 在升级前返回 HTTP 401
104
+ → Endpoint 创建 Session { userID, memberID, customData }
105
+ → Endpoint middleware 通过 ctx.session 读取
106
+ ```
107
+
108
+ ticket 字符串本身不包含 `userID` 或 `customData`。在所有 HTTP 路由中,只有 session-ticket 请求会在 WebSocket 建连时把当前 `ctx.userID` 和 `ctx.customData` 带入 Session。
109
+
110
+ Endpoint middleware 通过 `ctx.session.userID` 和 `ctx.session.customData` 读取这些数据。`memberID` 由 Endpoint 为每个 WebSocket Session 生成,重连后会变化。
111
+
112
+ ```ts
113
+ import { CollabError } from '@univerjs-pro/collaboration-service';
114
+
115
+ endpoint.use('joinUnit', async (ctx, next) => {
116
+ // ctx.session.userID 和 ctx.session.customData 来自 session-ticket HTTP 请求。
117
+ if (!await acl.canRead(ctx.session.userID, ctx.unitID)) {
118
+ throw new CollabError('PERMISSION_DENIED', 'Unit is not accessible');
119
+ }
120
+
121
+ await next();
122
+ });
123
+ ```
124
+
125
+ ### `new-changes` 连接两组请求
126
+
127
+ new-changes 是 HTTP 文档数据请求,但它的 ACK 和广播通过 WebSocket 投递:
128
+
129
+ ```text
130
+ HTTP new-changes { memberID }
131
+ → Endpoint 使用 memberID 查找在线 Session
132
+ → 验证 session.userID === 当前 HTTP ctx.userID
133
+ → 验证 Session 已 JOIN 目标 Unit
134
+ → 使用当前 HTTP 请求的 ctx.userID 和 ctx.customData 调用 Service
135
+ → 通过 WebSocket 发送 ACK 和广播
136
+ ```
137
+
138
+ `memberID` 只用于定位 WebSocket Session,不能单独作为身份凭据。这些关联和校验由 Endpoint 完成,应用不需要自己处理。
139
+
140
+ ## Middleware
141
+
142
+ Endpoint 提供以下四个实时协议 middleware action。文档数据请求使用的 Service middleware 已在上一节说明,具体 action 见 `@univerjs-pro/collaboration-service` README。
143
+
144
+ | Action | 触发时机 | 可见字段 | 重试语义 | 典型用途 |
145
+ | --- | --- | --- | --- | --- |
146
+ | `connect` | 消费 ticket 并创建 Session 后,处理 `HELLO` 前 | `session`、`connection`、`member` | 每次建连一次;重连会再次执行 | 连接策略、补充成员名称和头像 |
147
+ | `joinUnit` | 收到 `JOIN` 后,Session 加入房间前 | `session`、`unitID` | 当前 Session 首次 JOIN 时一次;Endpoint 不重试 | 房间读取 ACL |
148
+ | `receivePresence` | 已 JOIN 的 Session 发送 Presence 后、向房间广播前 | `session`、`unitID`、`payload`、`customData` | 每条 Presence 一次;Endpoint 不重试 | 校验、限流和过滤 Presence |
149
+ | `sendPresence` | Presence 即将发送给某个房间成员前 | 上述字段、`targetMemberID` | 每个目标成员一次;发送失败不重试 | 按接收者过滤 Presence |
150
+
151
+ 表中的 `session` 来自 session-ticket 请求;Presence context 顶层的 `customData` 则是
152
+ Transport 为当前 WebSocket message 创建的独立对象。
153
+
154
+ 同一 action 的 middleware 按注册顺序执行;`await next()` 进入下一个 middleware,抛出
155
+ `CollabError` 表示预期拒绝。
156
+
157
+ `joinUnit` 只保护实时房间,不能取代 Service `readUnitData` middleware;未加入房间的客户端仍可能直接发起 snapshot HTTP 请求。
158
+
159
+ ## 协议路由参考
160
+
161
+ 下表使用默认 `protocolBasePath=/universer-api`。通常由 Univer Collaboration Client 调用这些路由,应用不需要手动封装。
162
+
163
+ | 通道 | Method / Message | Path | 用途 |
164
+ | --- | --- | --- | --- |
165
+ | HTTP | `GET` | `/universer-api/user/session-ticket` | 获取一次性 WebSocket ticket |
166
+ | HTTP | `GET` | `/universer-api/snapshot/:type/unit/:unitID/rev/:revision` | 加载 Unit snapshot 和 changesets |
167
+ | HTTP | `GET` | `/universer-api/snapshot/:type/unit/:unitID/block/:blockID` | 加载 Unit block |
168
+ | HTTP | `GET` | `/universer-api/snapshot/block/:type/unit/:unitID/block/:blockID` | 加载已解析的 Unit block |
169
+ | HTTP | `GET` | `/universer-api/snapshot/:type/unit/:unitID/fetchmissing?from=:from&to=:to` | 加载缺失 changesets |
170
+ | HTTP | `POST` | `/universer-api/comb/:type/unit/:unitID/new_changes` | 提交 changeset |
171
+ | HTTP | `DELETE` | `/universer-api/snapshot/-/units` | 删除 Unit |
172
+ | HTTP | `POST` | `/universer-api/snapshot/-/units/recover` | 恢复 Unit |
173
+ | WebSocket open | `GET` + Upgrade | `/universer-api/comb/connect?sessionTicket=:ticket` | 消费 ticket 并建立 Session |
174
+ | WebSocket | `HELLO/HEARTBEAT/JOIN/LEAVE/Presence` | `/universer-api/comb/connect` | 成员、房间和 Presence |
175
+
176
+ ## 配置与部署
177
+
178
+ ```ts
179
+ const endpoint = new UniverCollabEndpoint(collabService, {
180
+ protocolBasePath: '/universer-api', // 默认值
181
+ sessionTicketTtlMs: 5 * 60_000, // 默认 5 分钟
182
+ });
183
+ ```
184
+
185
+ Session、Unit room 和待发送消息队列都在进程内,因此实时广播只保证单 Endpoint 进程。当前没有跨进程的在线成员、房间或广播能力。
186
+
187
+ WebSocket ACK 和广播不保证每条消息都送达,也不是权威数据源。客户端断线后使用 revision
188
+ 和 fetch-missing HTTP 补齐 confirmed changesets。
189
+
190
+ ### 停止服务
191
+
192
+ Transport 会释放注册的 Endpoint;Endpoint 会清理 Session、房间和 Service event 订阅。应用仍需释放 Service 和 Database Adapter。
193
+
194
+ ```ts
195
+ await transport.dispose();
196
+ await collabService.dispose();
197
+ await database.dispose();
198
+ ```
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object['defineProperty'](exports,Symbol['toStringTag'],{'value':'Module'});let e=require('node:crypto'),t=require('@univerjs-pro/collaboration-service'),n=require('@univerjs/protocol'),r=require('@univerjs-pro/collaboration-transport-node');var i={'CHANGESET_ACK':'changeset_ack','CHANGESET_REJ':'changeset_rej','CHANGESET_SHOULD_RETRY':'changeset_should_retry','NEW_CHANGESETS':'new_changesets','PERMISSION_REJ':'permission_rej','UPDATE_CURSOR':'update_cursor','USERS_ENTER':'users_enter','USERS_LEAVE':'users_leave'};function a(_0x370184,_0x253e83,_0xd99ea7=''){return{'cmd':_0x370184,'code':n['CmdRspCode']['OK'],'reason':'success','routeKey':_0xd99ea7,'infoRsp':{'memberID':_0x253e83}};}function o(_0x4b71fe,_0x286ca5,_0x2464f2,_0x5c3cb4){return{'cmd':n['CombCmd']['JOIN'],'code':_0x286ca5,'reason':_0x5c3cb4,'routeKey':_0x4b71fe,'joinRsp':{'roomInfos':_0x2464f2}};}function s(_0x2a75ac){return{'memberID':_0x2a75ac['memberID'],'userID':_0x2a75ac['userID'],'name':_0x2a75ac['name'],..._0x2a75ac['avatar']===void 0x0?{}:{'avatar':_0x2a75ac['avatar']}};}function c(_0x9d0037,_0x26ed9f){return{'cmd':n['CombCmd']['RECV'],'code':n['CmdRspCode']['OK'],'reason':'success','routeKey':_0x9d0037,'collaMsg':_0x26ed9f};}function l(_0x48e551,_0xa6f2da){return c(_0x48e551,{'eventID':i['USERS_ENTER'],'joinEvent':{..._0xa6f2da,'avatar':_0xa6f2da['avatar']??''}});}function u(_0x35ffc8,_0x472977){return c(_0x35ffc8,{'eventID':i['USERS_LEAVE'],'leaveEvent':_0x472977});}function d(_0x27cae6,_0x236aa4){return c(_0x27cae6,{'eventID':i['UPDATE_CURSOR'],'updateCursorEvent':_0x236aa4});}function f(_0x31b935,_0x574bc8,_0x71ce7a){let _0x26f816={[i['CHANGESET_ACK']]:'csAckEvent',[i['CHANGESET_REJ']]:'csRejEvent',[i['CHANGESET_SHOULD_RETRY']]:'csShouldRetryEvent',[i['NEW_CHANGESETS']]:'newCsEvent',[i['PERMISSION_REJ']]:'permissionRejEvent'}[_0x574bc8];return c(_0x31b935,{'eventID':_0x574bc8,[_0x26f816]:{'cs':_0x71ce7a}});}function p(_0x4c9124,_0x6faf54,_0x5b76ee){return c(_0x4c9124,{..._0x5b76ee,'eventID':_0x6faf54});}var m=class{['_middlewares']=new Map();['use'](_0x5a12e8,_0x4fc2f2){let _0x3d0c10=this['_middlewares']['get'](_0x5a12e8)??[],_0x1e5f2c=_0x4fc2f2;_0x3d0c10['push'](_0x1e5f2c),this['_middlewares']['set'](_0x5a12e8,_0x3d0c10);let _0x55b6fc=!0x1;return{'dispose':()=>{if(_0x55b6fc)return;_0x55b6fc=!0x0;let _0x3be693=_0x3d0c10['indexOf'](_0x1e5f2c);_0x3be693>=0x0&&_0x3d0c10['splice'](_0x3be693,0x1);}};}async['run'](_0x8b341,_0x341bec,_0x5de164){let _0x17c0fb=[...this['_middlewares']['get'](_0x8b341)??[]],_0x57f15a=async _0x4cf398=>{let _0x6054c9=_0x17c0fb[_0x4cf398];if(!_0x6054c9){await _0x5de164();return;}let _0x53c470=!0x1,_0x25bda9,_0x1c3dc6=()=>{if(_0x53c470)throw Error('Endpoint\x20middleware\x20for\x20'+String(_0x8b341)+'\x20called\x20next()\x20more\x20than\x20once');return _0x53c470=!0x0,_0x25bda9=_0x57f15a(_0x4cf398+0x1),_0x25bda9;};try{await _0x6054c9(_0x341bec,_0x1c3dc6);}catch(_0x230e8c){if(_0x25bda9)try{await _0x25bda9;}catch{}throw _0x230e8c;}if(!_0x53c470)throw Error('Endpoint\x20middleware\x20for\x20'+String(_0x8b341)+'\x20completed\x20without\x20calling\x20next()');await _0x25bda9;};await _0x57f15a(0x0);}['clear'](){this['_middlewares']['clear']();}},h={'code':n['ErrorCode']['OK'],'message':''};function g(_0x278d60,_0x3852b7){if(!/^-?\d+$/['test'](_0x278d60))throw new t['CollabError']('INVALID_REQUEST',_0x3852b7+'\x20must\x20be\x20an\x20integer');let _0x3b1547=Number(_0x278d60);if(!Number['isSafeInteger'](_0x3b1547))throw new t['CollabError']('INVALID_REQUEST',_0x3852b7+'\x20is\x20outside\x20the\x20safe\x20integer\x20range');return _0x3b1547;}function _(_0x27f0fa){if(_0x27f0fa instanceof r['NodeTransportError'])return _0x27f0fa['code']==='REQUEST_BODY_TOO_LARGE'?{'status':0x19d,'error':{'code':n['ErrorCode']['INVALID_ARGUMENT'],'message':_0x27f0fa['message']}}:_0x27f0fa['code']==='INVALID_JSON'||_0x27f0fa['code']==='REQUEST_ABORTED'?{'status':0x190,'error':{'code':n['ErrorCode']['INVALID_ARGUMENT'],'message':_0x27f0fa['message']}}:{'status':0x1f7,'error':{'code':n['ErrorCode']['INTERNAL_ERROR'],'message':'Transport\x20unavailable'}};if(!(_0x27f0fa instanceof t['CollabError']))return{'status':0x1f4,'error':{'code':n['ErrorCode']['INTERNAL_ERROR'],'message':'Internal\x20server\x20error'}};let _0x5d841d={'UNAUTHENTICATED':{'status':0x191,'code':n['ErrorCode']['UNAUTHENTICATED']},'INVALID_REQUEST':{'status':0x190,'code':n['ErrorCode']['INVALID_ARGUMENT']},'UNIT_NOT_FOUND':{'status':0x194,'code':n['ErrorCode']['NOT_FOUND']},'PERMISSION_DENIED':{'status':0x193,'code':n['ErrorCode']['PERMISSION_DENIED']},'OT_CONFLICT':{'status':0x199,'code':n['ErrorCode']['APPLY_REJECT']},'REVISION_MISMATCH':{'status':0x199,'code':n['ErrorCode']['CHANGESET_REVISION_CONFILICT']},'ADAPTER_FAILURE':{'status':0x1f7,'code':n['ErrorCode']['INTERNAL_ERROR']},'INTERNAL_ERROR':{'status':0x1f4,'code':n['ErrorCode']['INTERNAL_ERROR']}}[_0x27f0fa['code']];return{'status':_0x5d841d['status'],'error':{'code':_0x5d841d['code'],'message':_0x27f0fa['message']}};}function v(_0x4a02a0,_0x164c46,_0x2dbce9){_0x4a02a0['writableEnded']||_0x4a02a0['destroyed']||(_0x4a02a0['statusCode']=_0x164c46,_0x4a02a0['setHeader']('content-type','application/json;\x20charset=utf-8'),_0x4a02a0['end'](JSON['stringify'](_0x2dbce9,(_0x52f64d,_0x3e9741)=>_0x3e9741 instanceof Uint8Array?Buffer['from'](_0x3e9741)['toString']('base64'):_0x3e9741)));}var y=class{['_tickets']=new Map();['_disposed']=!0x1;async['issue'](_0x30a156,_0x926f3b){if(this['_disposed'])throw Error('Session\x20ticket\x20store\x20is\x20disposed');if(!Number['isSafeInteger'](_0x926f3b)||_0x926f3b<=0x0)throw TypeError('Session\x20ticket\x20TTL\x20must\x20be\x20a\x20positive\x20safe\x20integer');this['_purgeExpired']();let _0x2e4f04;do _0x2e4f04=(0x0,e['randomBytes'])(0x20)['toString']('base64url');while(this['_tickets']['has'](_0x2e4f04));return this['_tickets']['set'](_0x2e4f04,{'record':_0x30a156,'expiresAt':Date['now']()+_0x926f3b}),_0x2e4f04;}async['consume'](_0x51c89d){if(this['_disposed']||!_0x51c89d)return null;let _0x4c9f17=this['_tickets']['get'](_0x51c89d);return!_0x4c9f17||(this['_tickets']['delete'](_0x51c89d),_0x4c9f17['expiresAt']<=Date['now']())?null:_0x4c9f17['record'];}async['dispose'](){this['_disposed']||(this['_disposed']=!0x0,this['_tickets']['clear']());}['_purgeExpired'](){let _0x1a966e=Date['now']();for(let [_0x4a9fb5,_0x1e3c7f]of this['_tickets'])_0x1e3c7f['expiresAt']<=_0x1a966e&&this['_tickets']['delete'](_0x4a9fb5);}},b=0x5*0xea60;function x(_0x3069a9){try{return decodeURIComponent(_0x3069a9);}catch{throw new t['CollabError']('INVALID_REQUEST','Route\x20contains\x20invalid\x20encoding');}}function S(_0x25446f,_0x13a78d,_0x27ff7b){let _0x28fde9=C(_0x13a78d,_0x27ff7b);if(_0x28fde9===null)return null;if(_0x25446f==='GET'&&_0x28fde9==='/user/session-ticket')return{'name':'ticket','params':{}};if(_0x25446f==='DELETE'&&_0x28fde9==='/snapshot/-/units')return{'name':'delete-units','params':{}};if(_0x25446f==='POST'&&_0x28fde9==='/snapshot/-/units/recover')return{'name':'recover-units','params':{}};let _0xb69e89=_0x28fde9['split']('/')['filter'](Boolean);if(_0x25446f==='POST'&&_0xb69e89['length']===0x5&&_0xb69e89[0x0]==='comb'&&_0xb69e89[0x2]==='unit'&&_0xb69e89[0x4]==='new_changes')return{'name':'new-changes','params':{'type':x(_0xb69e89[0x1]),'unitID':x(_0xb69e89[0x3])}};if(_0x25446f!=='GET'||_0xb69e89[0x0]!=='snapshot')return null;if(_0xb69e89['length']===0x7&&_0xb69e89[0x1]==='block'&&_0xb69e89[0x3]==='unit'&&_0xb69e89[0x5]==='block')return{'name':'deserialized-block','params':{'type':x(_0xb69e89[0x2]),'unitID':x(_0xb69e89[0x4]),'blockID':x(_0xb69e89[0x6])}};if(_0xb69e89['length']<0x5||_0xb69e89[0x2]!=='unit')return null;let _0xaf50cb={'type':x(_0xb69e89[0x1]),'unitID':x(_0xb69e89[0x3])};return _0xb69e89['length']===0x6&&_0xb69e89[0x4]==='rev'?{'name':'unit','params':{..._0xaf50cb,'revision':x(_0xb69e89[0x5])}}:_0xb69e89['length']===0x6&&_0xb69e89[0x4]==='block'?{'name':'block','params':{..._0xaf50cb,'blockID':x(_0xb69e89[0x5])}}:_0xb69e89['length']===0x5&&_0xb69e89[0x4]==='fetchmissing'?{'name':'fetch-missing','params':_0xaf50cb}:null;}function C(_0x19b345,_0x1cefcc){if(_0x19b345===_0x1cefcc)return'/';let _0x71c34=_0x1cefcc+'/';return _0x19b345['startsWith'](_0x71c34)?_0x19b345['slice'](_0x1cefcc['length']):null;}function w(_0x165b10){if(!_0x165b10['startsWith']('/')||_0x165b10==='/'||_0x165b10['endsWith']('/')||_0x165b10['includes']('?')||_0x165b10['includes']('#'))throw TypeError('protocolBasePath\x20must\x20be\x20an\x20absolute\x20path\x20without\x20a\x20trailing\x20slash');return _0x165b10;}function T(_0x8da64a){return{'memberId':'http-'+(0x0,e['randomUUID'])(),'userId':_0x8da64a,'customData':{}};}function E(_0x43dad4){if(!_0x43dad4||typeof _0x43dad4!='object')throw new t['CollabError']('INVALID_REQUEST','Request\x20body\x20must\x20be\x20an\x20object');let _0x4241ce=_0x43dad4;if(typeof _0x4241ce['unitID']!='string'||typeof _0x4241ce['memberID']!='string'||!Number['isSafeInteger'](_0x4241ce['type'])||!_0x4241ce['changeset']||typeof _0x4241ce['changeset']!='object')throw new t['CollabError']('INVALID_REQUEST','Invalid\x20new\x20changes\x20request');}function D(_0x1420b9){if(!Array['isArray'](_0x1420b9)||_0x1420b9['length']===0x0||_0x1420b9['some'](_0x358109=>typeof _0x358109!='string'||_0x358109['trim']()['length']===0x0))throw new t['CollabError']('INVALID_REQUEST','unitIds\x20must\x20be\x20a\x20non-empty\x20string\x20array');}function O(_0x1453a6){let _0x124973=_0x1453a6['getAll']('unitIds');D(_0x124973);let _0x26e9d9=_0x1453a6['getAll']('hardDelete');if(_0x26e9d9['length']>0x1||_0x26e9d9['length']===0x1&&_0x26e9d9[0x0]!=='true'&&_0x26e9d9[0x0]!=='false')throw new t['CollabError']('INVALID_REQUEST','hardDelete\x20must\x20be\x20true\x20or\x20false');return{'unitIds':_0x124973,'hardDelete':_0x26e9d9[0x0]==='true'};}function k(_0x5e78f1){if(!_0x5e78f1||typeof _0x5e78f1!='object')throw new t['CollabError']('INVALID_REQUEST','Request\x20body\x20must\x20be\x20an\x20object');D(_0x5e78f1['unitIds']);}var A=class{['_service'];['_middleware']=new m();['_listeners']=new Map();['_bindingsByConnection']=new Map();['_claimedConnectionIDs']=new Set();['_bindingsByMember']=new Map();['_rooms']=new Map();['_outboundQueues']=new Map();['_ticketStore'];['_ownsTicketStore'];['_ticketTtlMs'];['_protocolBasePath'];['_combPath'];['_serviceSubscriptions'];['_deletedUnitIDs']=new Set();['_disposed']=!0x1;constructor(_0x1bd4ec,_0x267fa0={}){if(this['_service']=_0x1bd4ec,!_0x1bd4ec)throw TypeError('UniverCollabEndpoint\x20requires\x20a\x20service');if(this['_ticketStore']=_0x267fa0['ticketStore']??new y(),this['_ownsTicketStore']=_0x267fa0['ticketStore']===void 0x0,this['_ticketTtlMs']=_0x267fa0['sessionTicketTtlMs']??b,!Number['isSafeInteger'](this['_ticketTtlMs'])||this['_ticketTtlMs']<=0x0)throw TypeError('sessionTicketTtlMs\x20must\x20be\x20a\x20positive\x20safe\x20integer');this['_protocolBasePath']=w(_0x267fa0['protocolBasePath']??'/universer-api'),this['_combPath']=this['_protocolBasePath']+'/comb/connect',this['_serviceSubscriptions']=[this['_service']['on']('changesetCommitted',_0x163b3d=>{this['_enqueueCommittedChangeset'](_0x163b3d['changeset'],_0x163b3d['session']['memberId']);}),this['_service']['on']('unitsDeleted',_0x5b05ad=>{for(let _0x441c54 of _0x5b05ad['request']['unitIDs'])this['_deletedUnitIDs']['add'](_0x441c54);for(let _0x54331a of _0x5b05ad['request']['unitIDs'])this['_enqueueUnitOutbound'](_0x54331a,async()=>{this['_evictUnitRoom'](_0x54331a);});}),this['_service']['on']('unitsRecovered',async _0x3f34b5=>{await Promise['all'](_0x3f34b5['request']['unitIDs']['map'](_0x596aab=>this['_enqueueUnitOutbound'](_0x596aab,async()=>{this['_deletedUnitIDs']['delete'](_0x596aab);})));})];}['use'](_0x4dc213,_0xc658f4){return this['_assertRunning'](),this['_middleware']['use'](_0x4dc213,_0xc658f4);}['on'](_0x3b68c0,_0xef4f06){this['_assertRunning']();let _0x5c188a=this['_listeners']['get'](_0x3b68c0)??new Set(),_0x410b79=_0xef4f06;_0x5c188a['add'](_0x410b79),this['_listeners']['set'](_0x3b68c0,_0x5c188a);let _0x3373f8=!0x1;return{'dispose':()=>{_0x3373f8||(_0x3373f8=!0x0,_0x5c188a['delete'](_0x410b79));}};}['findJoinedSession'](_0x40c1d4){if(this['_disposed'])return null;let _0x2613ec=this['_bindingsByMember']['get'](_0x40c1d4['memberID']);return!_0x2613ec||_0x2613ec['session']['userId']!==_0x40c1d4['authenticatedUserID']||!_0x2613ec['joinedUnitIDs']['has'](_0x40c1d4['unitID'])?null:_0x2613ec['session'];}['findJoinedSessionByUser'](_0x3a4c6e){if(this['_disposed'])return null;for(let _0x18c089 of this['_rooms']['get'](_0x3a4c6e['unitID'])?.['values']()??[])if(_0x18c089['session']['userId']===_0x3a4c6e['authenticatedUserID'])return _0x18c089['session'];return null;}async['publishTrustedUnitEvent'](_0x1b2782){this['_assertRunning']();let _0x5066f9=[...this['_rooms']['get'](_0x1b2782['unitID'])?.['keys']()??[]],_0x1eaac7=this['_bindingsByMember']['get'](_0x1b2782['origin']['memberId'])?.['joinedUnitIDs']['has'](_0x1b2782['unitID'])?_0x1b2782['origin']['memberId']:void 0x0,_0x7b4f71=p(_0x1b2782['unitID'],_0x1b2782['eventID'],_0x1b2782['data']);this['_enqueueUnitOutbound'](_0x1b2782['unitID'],async()=>{for(let _0x59bb55 of _0x5066f9){if(_0x59bb55===_0x1eaac7)continue;let _0x43940d=this['_bindingsByMember']['get'](_0x59bb55);_0x43940d?.['joinedUnitIDs']['has'](_0x1b2782['unitID'])&&await this['_safeSend'](_0x43940d['connection'],_0x7b4f71);}});}async['handle'](_0x5a36b1,_0x496a09){if(this['_disposed']){await _0x496a09();return;}switch(_0x5a36b1['kind']){case'http':await this['_handleHttp'](_0x5a36b1,_0x496a09);return;case'websocket-open':await this['_handleOpen'](_0x5a36b1['incomingMessage']['url'],_0x5a36b1['connection'],_0x496a09);return;case'websocket-close':await this['_disconnectConnection'](_0x5a36b1['connection'],'disconnect')||await _0x496a09();return;case'websocket-message':await this['_handleMessage'](_0x5a36b1,_0x496a09);return;}}async['dispose'](){if(this['_disposed'])return;this['_disposed']=!0x0;for(let _0x224955 of this['_serviceSubscriptions'])_0x224955['dispose']();await Promise['allSettled']([...this['_outboundQueues']['values']()]);let _0x970420=[...this['_bindingsByConnection']['values']()];for(let _0x5cf06a of _0x970420)await this['_disconnectBinding'](_0x5cf06a,'endpoint-dispose'),_0x5cf06a['connection']['close'](0x3e9,'Endpoint\x20disposed');this['_bindingsByConnection']['clear'](),this['_claimedConnectionIDs']['clear'](),this['_bindingsByMember']['clear'](),this['_rooms']['clear'](),this['_outboundQueues']['clear'](),this['_middleware']['clear'](),this['_listeners']['clear'](),this['_ownsTicketStore']&&await this['_ticketStore']['dispose']?.();}async['_handleHttp'](_0x207a76,_0xe64ba2){let _0x51e114,_0x4622c5;try{_0x51e114=new URL(_0x207a76['incomingMessage']['url']??'/','http://localhost'),_0x4622c5=S(_0x207a76['incomingMessage']['method'],_0x51e114['pathname'],this['_protocolBasePath']);}catch(_0x7ff733){let _0x3b147b=_(_0x7ff733);v(_0x207a76['response'],_0x3b147b['status'],{'error':_0x3b147b['error']});return;}if(!_0x4622c5){await _0xe64ba2();return;}try{if(!_0x207a76['userId'])throw new t['CollabError']('UNAUTHENTICATED','HTTP\x20user\x20is\x20not\x20authenticated');if(_0x4622c5['name']==='ticket'){let _0x448acf=await this['_ticketStore']['issue']({'userId':_0x207a76['userId'],'customData':_0x207a76['customData']},this['_ticketTtlMs']);v(_0x207a76['response'],0xc8,{'error':h,'ticket':_0x448acf});return;}let _0x10d64d={'session':T(_0x207a76['userId']),'customData':_0x207a76['customData']};if(_0x4622c5['name']==='delete-units'){let _0x15f8d9=O(_0x51e114['searchParams']);await this['_service']['deleteUnits']({'unitIDs':_0x15f8d9['unitIds'],'hardDelete':_0x15f8d9['hardDelete']},_0x10d64d),v(_0x207a76['response'],0xc8,{'error':h});return;}if(_0x4622c5['name']==='recover-units'){let _0x122330=await _0x207a76['readJson']();k(_0x122330),await this['_service']['recoverUnits']({'unitIDs':_0x122330['unitIds']},_0x10d64d),v(_0x207a76['response'],0xc8,{'error':h});return;}let _0x4f837f=g(_0x4622c5['params']['type'],'type'),_0x28a6a7=_0x4622c5['params']['unitID'];if(_0x4622c5['name']==='new-changes'){await this['_handleNewChanges'](_0x207a76,_0x28a6a7,_0x4f837f);return;}switch(_0x4622c5['name']){case'unit':{let _0x4d1338=g(_0x4622c5['params']['revision'],'revision'),_0x594d46=await this['_service']['getUnit']({'unitID':_0x28a6a7,'type':_0x4f837f,'revision':_0x4d1338},_0x10d64d);v(_0x207a76['response'],0xc8,{'error':h,'snapshot':_0x594d46['snapshot'],'changesets':_0x594d46['changesets']});return;}case'block':case'deserialized-block':{let _0x27a07e=await this['_service']['getSheetBlock']({'unitID':_0x28a6a7,'type':_0x4f837f,'blockID':_0x4622c5['params']['blockID']},_0x10d64d);if(!_0x27a07e['block'])throw new t['CollabError']('UNIT_NOT_FOUND','Sheet\x20block\x20was\x20not\x20found');let _0x49ca1a=_0x4622c5['name']==='deserialized-block'?{..._0x27a07e['block'],'data':JSON['parse'](new TextDecoder()['decode'](_0x27a07e['block']['data']))}:_0x27a07e['block'];v(_0x207a76['response'],0xc8,{'error':h,'block':_0x49ca1a});return;}case'fetch-missing':{let _0x53f47d=g(_0x51e114['searchParams']['get']('from')??'','from'),_0x560818=g(_0x51e114['searchParams']['get']('to')??'','to'),_0x1bda13=await this['_service']['getChangesets']({'unitID':_0x28a6a7,'type':_0x4f837f,'from':_0x53f47d,'to':_0x560818},_0x10d64d);v(_0x207a76['response'],0xc8,{'error':h,'changesets':_0x1bda13['changesets'],'latestRevision':_0x1bda13['latestRevision']});return;}}}catch(_0x1e1477){let _0x44c78f=_(_0x1e1477 instanceof SyntaxError?new t['CollabError']('INTERNAL_ERROR','Stored\x20sheet\x20block\x20is\x20not\x20valid\x20JSON',{'cause':_0x1e1477}):_0x1e1477);v(_0x207a76['response'],_0x44c78f['status'],{'error':_0x44c78f['error']});}}async['_handleOpen'](_0x76ce3c,_0x777e5,_0x31681a){let _0x46ccb6;try{_0x46ccb6=new URL(_0x76ce3c??'/','http://localhost');}catch{_0x777e5['close'](0x3f0,'Invalid\x20WebSocket\x20URL');return;}if(_0x46ccb6['pathname']!==this['_combPath']){await _0x31681a();return;}this['_claimedConnectionIDs']['add'](_0x777e5['id']);try{let _0x4400eb=_0x46ccb6['searchParams']['get']('sessionTicket')??'',_0x1cc0e9=await this['_ticketStore']['consume'](_0x4400eb);if(!_0x1cc0e9){_0x777e5['close'](0x3f0,'Invalid\x20or\x20expired\x20session\x20ticket');return;}let _0x18994f=(0x0,e['randomUUID'])(),_0x2eb9b1={'memberId':_0x18994f,'userId':_0x1cc0e9['userId'],'customData':_0x1cc0e9['customData']},_0x20ea22={'memberID':_0x18994f,'userID':_0x1cc0e9['userId'],'name':_0x1cc0e9['userId']},_0x3138cf={'connection':_0x777e5,'session':_0x2eb9b1,'member':_0x20ea22};await this['_middleware']['run']('connect',_0x3138cf,()=>void 0x0);let _0x3a19db={'connection':_0x777e5,'session':_0x2eb9b1,'member':_0x20ea22,'joinedUnitIDs':new Set()};this['_bindingsByConnection']['set'](_0x777e5['id'],_0x3a19db),this['_bindingsByMember']['set'](_0x18994f,_0x3a19db);}catch(_0x1c9ff6){_0x777e5['close'](_0x1c9ff6 instanceof t['CollabError']?0x3f0:0x3f3,_0x1c9ff6 instanceof t['CollabError']?'Connection\x20rejected':'Endpoint\x20failure');}}async['_handleMessage'](_0x50679f,_0x6a6a09){let _0x278c04=this['_bindingsByConnection']['get'](_0x50679f['connection']['id']);if(!_0x278c04){if(!this['_claimedConnectionIDs']['has'](_0x50679f['connection']['id'])){await _0x6a6a09();return;}_0x50679f['connection']['close'](0x3f0,'WebSocket\x20Session\x20is\x20not\x20established');return;}if(typeof _0x50679f['data']!='string'){_0x50679f['connection']['close'](0x3eb,'Comb\x20messages\x20must\x20be\x20JSON\x20text');return;}let _0xbee974;try{let _0x3d1cf9=JSON['parse'](_0x50679f['data']);if(!_0x3d1cf9||typeof _0x3d1cf9!='object')throw Error('invalid');_0xbee974=_0x3d1cf9;}catch{_0x50679f['connection']['close'](0x3eb,'Invalid\x20Comb\x20JSON\x20message');return;}let _0x302535=typeof _0xbee974['routeKey']=='string'?_0xbee974['routeKey']:'';switch(_0xbee974['cmd']){case n['CombCmd']['HELLO']:case n['CombCmd']['HEARTBEAT']:await _0x50679f['connection']['send'](JSON['stringify'](a(_0xbee974['cmd'],_0x278c04['member']['memberID'],_0x302535)));return;case n['CombCmd']['JOIN']:await this['_handleJoin'](_0x278c04,_0xbee974,_0x302535);return;case n['CombCmd']['LEAVE']:await this['_handleLeave'](_0x278c04,_0xbee974,_0x302535);return;case n['CombCmd']['INGEST']:if(_0xbee974['collaMsg']?.['eventID']!==i['UPDATE_CURSOR']){await _0x6a6a09();return;}await this['_handlePresence'](_0x278c04,_0xbee974,_0x50679f['customData']);return;default:await _0x6a6a09();}}async['_handleJoin'](_0x55172e,_0x33ed98,_0x2b52d5){let _0x3ef4e7=_0x33ed98['joinReq']?.['rooms'],_0x49974a=_0x3ef4e7?.['length']?_0x3ef4e7['map'](_0x2f9cc6=>_0x2f9cc6['roomID']):_0x2b52d5?[_0x2b52d5]:[];if(_0x49974a['length']===0x0||_0x49974a['some'](_0x5ede6d=>typeof _0x5ede6d!='string'||!_0x5ede6d)){await _0x55172e['connection']['send'](JSON['stringify'](o(_0x2b52d5,n['CmdRspCode']['FAIL'],{},'Invalid\x20JOIN\x20request')));return;}let _0x1ddb33=[...new Set(_0x49974a)],_0x30f465=_0x1ddb33['filter'](_0x1f7abb=>!_0x55172e['joinedUnitIDs']['has'](_0x1f7abb));try{for(let _0x2e2f0f of _0x1ddb33)this['_assertJoinable'](_0x2e2f0f);for(let _0x3e1a9e of _0x30f465)await this['_middleware']['run']('joinUnit',{'session':_0x55172e['session'],'unitID':_0x3e1a9e},()=>void 0x0);for(let _0x434ebb of _0x1ddb33)this['_assertJoinable'](_0x434ebb);}catch(_0x140cc5){let _0x1a4a32=_0x140cc5 instanceof t['CollabError']&&_0x140cc5['code']==='UNIT_NOT_FOUND'?n['CmdRspCode']['JOIN_ROOM_NOT_EXISTS']:_0x140cc5 instanceof t['CollabError']&&(_0x140cc5['code']==='PERMISSION_DENIED'||_0x140cc5['code']==='UNAUTHENTICATED')?n['CmdRspCode']['JOIN_ROOM_PERMISSION_DENIED']:n['CmdRspCode']['FAIL'];await _0x55172e['connection']['send'](JSON['stringify'](o(_0x2b52d5||_0x1ddb33[0x0],_0x1a4a32,{},_0x140cc5 instanceof t['CollabError']?_0x140cc5['message']:'JOIN\x20failed')));return;}for(let _0x33b1f5 of _0x30f465){let _0xa5ad39=this['_rooms']['get'](_0x33b1f5)??new Map();_0xa5ad39['set'](_0x55172e['member']['memberID'],_0x55172e),this['_rooms']['set'](_0x33b1f5,_0xa5ad39),_0x55172e['joinedUnitIDs']['add'](_0x33b1f5);}let _0x5529f2={};for(let _0x519565 of _0x1ddb33)_0x5529f2[_0x519565]={'roomID':_0x519565,'members':[...this['_rooms']['get'](_0x519565)?.['values']()??[]]['map'](_0x327b05=>s(_0x327b05['member']))};await _0x55172e['connection']['send'](JSON['stringify'](o(_0x2b52d5||_0x1ddb33[0x0],n['CmdRspCode']['OK'],_0x5529f2,'success')));for(let _0x43c95d of _0x30f465)await this['_broadcastToRoom'](_0x43c95d,l(_0x43c95d,s(_0x55172e['member'])),_0x55172e['member']['memberID']);}async['_handleLeave'](_0x2d7361,_0x3d627f,_0x3fb1d1){let _0x272595=typeof _0x3d627f['leaveReq']?.['roomID']=='string'?_0x3d627f['leaveReq']['roomID']:_0x3fb1d1;_0x272595&&await this['_leaveUnit'](_0x2d7361,_0x272595,'explicit');}async['_handlePresence'](_0x12a1d1,_0x223654,_0x4a03a9){let _0xdf9be9=typeof _0x223654['routeKey']=='string'?_0x223654['routeKey']:'',_0x53a444=_0x223654['collaMsg']?.['updateCursorEvent']?.['selection'];if(!_0xdf9be9||!_0x12a1d1['joinedUnitIDs']['has'](_0xdf9be9)||typeof _0x53a444!='string')return;let _0x2a8dbb={'unitID':_0xdf9be9,'memberID':_0x12a1d1['member']['memberID'],'selection':_0x53a444};try{await this['_middleware']['run']('receivePresence',{'session':_0x12a1d1['session'],'unitID':_0xdf9be9,'payload':_0x2a8dbb,'customData':_0x4a03a9},()=>void 0x0);}catch(_0x9f366f){if(_0x9f366f instanceof t['CollabError'])return;throw _0x9f366f;}if(typeof _0x2a8dbb['selection']!='string')return;let _0x147371=[...this['_rooms']['get'](_0xdf9be9)?.['values']()??[]]['filter'](_0x2b9574=>_0x2b9574!==_0x12a1d1);for(let _0x8b035b of _0x147371){let _0x3ed2e1={'unitID':_0xdf9be9,'memberID':_0x12a1d1['member']['memberID'],'selection':_0x2a8dbb['selection']};try{await this['_middleware']['run']('sendPresence',{'session':_0x12a1d1['session'],'unitID':_0xdf9be9,'payload':_0x3ed2e1,'customData':_0x4a03a9,'targetMemberId':_0x8b035b['member']['memberID']},()=>void 0x0);}catch{continue;}typeof _0x3ed2e1['selection']=='string'&&await this['_safeSend'](_0x8b035b['connection'],d(_0xdf9be9,{'unitID':_0xdf9be9,'memberID':_0x12a1d1['member']['memberID'],'selection':_0x3ed2e1['selection']}));}}async['_handleNewChanges'](_0x5d1d4c,_0x514619,_0xe4afcb){let _0x17abbb=await _0x5d1d4c['readJson']();if(E(_0x17abbb),_0x17abbb['unitID']!==_0x514619||_0x17abbb['type']!==_0xe4afcb||_0x17abbb['changeset']?.['unitID']!==_0x514619||_0x17abbb['changeset']['type']!==_0xe4afcb)throw new t['CollabError']('INVALID_REQUEST','Route,\x20request\x20and\x20changeset\x20identity\x20must\x20match');let _0x89246e=this['_bindingsByMember']['get'](_0x17abbb['memberID']);if(!_0x89246e||_0x89246e['session']['userId']!==_0x5d1d4c['userId'])throw new t['CollabError']('PERMISSION_DENIED','memberID\x20does\x20not\x20belong\x20to\x20the\x20authenticated\x20user');if(!_0x89246e['joinedUnitIDs']['has'](_0x514619))throw new t['CollabError']('PERMISSION_DENIED','Session\x20has\x20not\x20joined\x20this\x20unit');let _0x5c9105=await this['_service']['submitChangeset']({'changeset':_0x17abbb['changeset']},{'session':_0x89246e['session'],'customData':_0x5d1d4c['customData']});if(_0x5c9105['status']==='already-committed')await this['_safeSend'](_0x89246e['connection'],f(_0x514619,i['CHANGESET_ACK'],_0x5c9105['changeset']));else{if(_0x5c9105['status']==='rejected'){let _0x2aed34=_0x5c9105['error']['code']==='PERMISSION_DENIED'||_0x5c9105['error']['code']==='UNAUTHENTICATED'?i['PERMISSION_REJ']:i['CHANGESET_REJ'];await this['_safeSend'](_0x89246e['connection'],f(_0x514619,_0x2aed34,this['_trustedClientChangeset'](_0x17abbb['changeset'],_0x89246e)));}else _0x5c9105['status']==='retry'&&await this['_safeSend'](_0x89246e['connection'],f(_0x514619,i['CHANGESET_SHOULD_RETRY'],this['_trustedClientChangeset'](_0x17abbb['changeset'],_0x89246e)));}v(_0x5d1d4c['response'],0xc8,{'error':h});}['_trustedClientChangeset'](_0x2da6c1,_0x49e8fe){return{..._0x2da6c1,'userID':_0x49e8fe['session']['userId'],'memberID':_0x49e8fe['session']['memberId']};}['_enqueueCommittedChangeset'](_0xd6015d,_0x5240e6){if(this['_disposed'])return;let _0x544701=_0xd6015d['unitID'],_0x242bc7=[...this['_rooms']['get'](_0x544701)?.['keys']()??[]];this['_enqueueUnitOutbound'](_0x544701,async()=>{for(let _0x35c0e8 of _0x242bc7){let _0x56ab67=this['_bindingsByMember']['get'](_0x35c0e8);if(!_0x56ab67?.['joinedUnitIDs']['has'](_0x544701))continue;let _0x32e4ae=_0x35c0e8===_0x5240e6?i['CHANGESET_ACK']:i['NEW_CHANGESETS'];await this['_safeSend'](_0x56ab67['connection'],f(_0x544701,_0x32e4ae,_0xd6015d));}});}['_evictUnitRoom'](_0x4e475d){let _0x223a61=this['_rooms']['get'](_0x4e475d);if(_0x223a61){this['_rooms']['delete'](_0x4e475d);for(let _0x57589a of _0x223a61['values']())_0x57589a['joinedUnitIDs']['delete'](_0x4e475d);}}['_enqueueUnitOutbound'](_0x415c71,_0x5c3b60){let _0x111316=(this['_outboundQueues']['get'](_0x415c71)??Promise['resolve']())['catch'](()=>void 0x0)['then'](_0x5c3b60);return this['_outboundQueues']['set'](_0x415c71,_0x111316),_0x111316['finally'](()=>{this['_outboundQueues']['get'](_0x415c71)===_0x111316&&this['_outboundQueues']['delete'](_0x415c71);}),_0x111316;}async['_disconnectConnection'](_0x12e280,_0x5545c8){let _0x4e88b8=this['_bindingsByConnection']['get'](_0x12e280['id']),_0x33b1b6=this['_claimedConnectionIDs']['delete'](_0x12e280['id']);return _0x4e88b8?(await this['_disconnectBinding'](_0x4e88b8,_0x5545c8),!0x0):_0x33b1b6;}async['_disconnectBinding'](_0xdc5127,_0x25976){this['_bindingsByConnection']['delete'](_0xdc5127['connection']['id']),this['_bindingsByMember']['delete'](_0xdc5127['member']['memberID']);let _0x447faf=[..._0xdc5127['joinedUnitIDs']];for(let _0x35ef3c of _0x447faf)await this['_leaveUnit'](_0xdc5127,_0x35ef3c,_0x25976);}async['_leaveUnit'](_0x217d37,_0x36fef0,_0x2d35de){if(!_0x217d37['joinedUnitIDs']['delete'](_0x36fef0))return!0x1;let _0xca82aa=this['_rooms']['get'](_0x36fef0);return _0xca82aa?.['delete'](_0x217d37['member']['memberID']),_0xca82aa?.['size']===0x0&&this['_rooms']['delete'](_0x36fef0),await this['_broadcastToRoom'](_0x36fef0,u(_0x36fef0,{'memberID':_0x217d37['member']['memberID'],'name':_0x217d37['member']['name']}),_0x217d37['member']['memberID']),await this['_emit']('memberLeftUnit',{'session':_0x217d37['session'],'unitID':_0x36fef0,'reason':_0x2d35de}),!0x0;}async['_broadcastToRoom'](_0x9fdb6a,_0x522ba4,_0x4267f5){let _0x475637=[...this['_rooms']['get'](_0x9fdb6a)?.['values']()??[]];await Promise['allSettled'](_0x475637['filter'](_0x5aaf5c=>_0x5aaf5c['member']['memberID']!==_0x4267f5)['map'](_0x73b04e=>this['_safeSend'](_0x73b04e['connection'],_0x522ba4)));}async['_safeSend'](_0x3443b1,_0x37b314){try{await _0x3443b1['send'](JSON['stringify'](_0x37b314));}catch{}}async['_emit'](_0x39a0f1,_0x42f4da){let _0xabf8f3=[...this['_listeners']['get'](_0x39a0f1)??[]];for(let _0xbf1c91 of _0xabf8f3)try{await _0xbf1c91(_0x42f4da);}catch{}}['_assertRunning'](){if(this['_disposed'])throw Error('UniverCollabEndpoint\x20is\x20disposed');}['_assertJoinable'](_0x5e1d01){if(this['_deletedUnitIDs']['has'](_0x5e1d01))throw new t['CollabError']('UNIT_NOT_FOUND','Unit\x20has\x20been\x20deleted');}};exports['MemorySessionTicketStore']=y,exports['UniverCollabEndpoint']=A;
1
+ Object['defineProperty'](exports,Symbol['toStringTag'],{'value':'Module'});let e=require('node:crypto'),t=require('@univerjs-pro/collaboration-service'),n=require('@univerjs/protocol'),r=require('@univerjs-pro/collaboration-transport-node');var i={'CHANGESET_ACK':'changeset_ack','CHANGESET_REJ':'changeset_rej','CHANGESET_SHOULD_RETRY':'changeset_should_retry','NEW_CHANGESETS':'new_changesets','PERMISSION_REJ':'permission_rej','UPDATE_CURSOR':'update_cursor','USERS_ENTER':'users_enter','USERS_LEAVE':'users_leave'};function a(_0x1a2566,_0x47f4d9,_0x58fd76=''){return{'cmd':_0x1a2566,'code':n['CmdRspCode']['OK'],'reason':'success','routeKey':_0x58fd76,'infoRsp':{'memberID':_0x47f4d9}};}function o(_0x2cd3c7,_0x3dcf30,_0x5c2ef0,_0xe7325a){return{'cmd':n['CombCmd']['JOIN'],'code':_0x3dcf30,'reason':_0xe7325a,'routeKey':_0x2cd3c7,'joinRsp':{'roomInfos':_0x5c2ef0}};}function s(_0x22607a){return{'memberID':_0x22607a['memberID'],'userID':_0x22607a['userID'],'name':_0x22607a['name'],..._0x22607a['avatar']===void 0x0?{}:{'avatar':_0x22607a['avatar']}};}function c(_0x4cf1a4,_0x167e7b){return{'cmd':n['CombCmd']['RECV'],'code':n['CmdRspCode']['OK'],'reason':'success','routeKey':_0x4cf1a4,'collaMsg':_0x167e7b};}function l(_0x416400,_0x37101d){return c(_0x416400,{'eventID':i['USERS_ENTER'],'joinEvent':{..._0x37101d,'avatar':_0x37101d['avatar']??''}});}function u(_0x55d23b,_0x69601f){return c(_0x55d23b,{'eventID':i['USERS_LEAVE'],'leaveEvent':_0x69601f});}function d(_0x88d713,_0x4db448){return c(_0x88d713,{'eventID':i['UPDATE_CURSOR'],'updateCursorEvent':_0x4db448});}function f(_0x4e978a,_0x4538f0,_0x240eb1){let _0x20efa4={[i['CHANGESET_ACK']]:'csAckEvent',[i['CHANGESET_REJ']]:'csRejEvent',[i['CHANGESET_SHOULD_RETRY']]:'csShouldRetryEvent',[i['NEW_CHANGESETS']]:'newCsEvent',[i['PERMISSION_REJ']]:'permissionRejEvent'}[_0x4538f0];return c(_0x4e978a,{'eventID':_0x4538f0,[_0x20efa4]:{'cs':_0x240eb1}});}function p(_0x44d1bc,_0xc3e448,_0x133496){return c(_0x44d1bc,{..._0x133496,'eventID':_0xc3e448});}var m=class{['_middlewares']=new Map();['use'](_0x16448b,_0x5cf3f6){let _0x1187a6=this['_middlewares']['get'](_0x16448b)??[],_0x4ece2b=_0x5cf3f6;_0x1187a6['push'](_0x4ece2b),this['_middlewares']['set'](_0x16448b,_0x1187a6);let _0x249256=!0x1;return{'dispose':()=>{if(_0x249256)return;_0x249256=!0x0;let _0x340114=_0x1187a6['indexOf'](_0x4ece2b);_0x340114>=0x0&&_0x1187a6['splice'](_0x340114,0x1);}};}async['run'](_0x470129,_0x5861d3,_0x4e0c18){let _0x17bc95=[...this['_middlewares']['get'](_0x470129)??[]],_0x1bd1b5=async _0x5b2136=>{let _0x23ce26=_0x17bc95[_0x5b2136];if(!_0x23ce26){await _0x4e0c18();return;}let _0x4889fa=!0x1,_0x4d905e,_0x20cfaa=()=>{if(_0x4889fa)throw Error('Endpoint\x20middleware\x20for\x20'+String(_0x470129)+'\x20called\x20next()\x20more\x20than\x20once');return _0x4889fa=!0x0,_0x4d905e=_0x1bd1b5(_0x5b2136+0x1),_0x4d905e;};try{await _0x23ce26(_0x5861d3,_0x20cfaa);}catch(_0x428634){if(_0x4d905e)try{await _0x4d905e;}catch{}throw _0x428634;}if(!_0x4889fa)throw Error('Endpoint\x20middleware\x20for\x20'+String(_0x470129)+'\x20completed\x20without\x20calling\x20next()');await _0x4d905e;};await _0x1bd1b5(0x0);}['clear'](){this['_middlewares']['clear']();}},h={'code':n['ErrorCode']['OK'],'message':''};function g(_0x1ca71f,_0x5373a4){if(!/^-?\d+$/['test'](_0x1ca71f))throw new t['CollabError']('INVALID_REQUEST',_0x5373a4+'\x20must\x20be\x20an\x20integer');let _0x1040f8=Number(_0x1ca71f);if(!Number['isSafeInteger'](_0x1040f8))throw new t['CollabError']('INVALID_REQUEST',_0x5373a4+'\x20is\x20outside\x20the\x20safe\x20integer\x20range');return _0x1040f8;}function _(_0x11e721){if(_0x11e721 instanceof r['NodeTransportError'])return _0x11e721['code']==='REQUEST_BODY_TOO_LARGE'?{'status':0x19d,'error':{'code':n['ErrorCode']['INVALID_ARGUMENT'],'message':_0x11e721['message']}}:_0x11e721['code']==='INVALID_JSON'||_0x11e721['code']==='REQUEST_ABORTED'?{'status':0x190,'error':{'code':n['ErrorCode']['INVALID_ARGUMENT'],'message':_0x11e721['message']}}:{'status':0x1f7,'error':{'code':n['ErrorCode']['INTERNAL_ERROR'],'message':'Transport\x20unavailable'}};if(!(_0x11e721 instanceof t['CollabError']))return{'status':0x1f4,'error':{'code':n['ErrorCode']['INTERNAL_ERROR'],'message':'Internal\x20server\x20error'}};let _0x43f84d={'UNAUTHENTICATED':{'status':0x191,'code':n['ErrorCode']['UNAUTHENTICATED']},'INVALID_REQUEST':{'status':0x190,'code':n['ErrorCode']['INVALID_ARGUMENT']},'UNIT_NOT_FOUND':{'status':0x194,'code':n['ErrorCode']['NOT_FOUND']},'PERMISSION_DENIED':{'status':0x193,'code':n['ErrorCode']['PERMISSION_DENIED']},'OT_CONFLICT':{'status':0x199,'code':n['ErrorCode']['APPLY_REJECT']},'REVISION_MISMATCH':{'status':0x199,'code':n['ErrorCode']['CHANGESET_REVISION_CONFILICT']},'ADAPTER_FAILURE':{'status':0x1f7,'code':n['ErrorCode']['INTERNAL_ERROR']},'INTERNAL_ERROR':{'status':0x1f4,'code':n['ErrorCode']['INTERNAL_ERROR']}}[_0x11e721['code']];return{'status':_0x43f84d['status'],'error':{'code':_0x43f84d['code'],'message':_0x11e721['message']}};}function v(_0x22dd95,_0x43316a,_0x15c863){_0x22dd95['writableEnded']||_0x22dd95['destroyed']||(_0x22dd95['statusCode']=_0x43316a,_0x22dd95['setHeader']('content-type','application/json;\x20charset=utf-8'),_0x22dd95['end'](JSON['stringify'](_0x15c863,(_0x4ee84f,_0x56b9e7)=>_0x56b9e7 instanceof Uint8Array?Buffer['from'](_0x56b9e7)['toString']('base64'):_0x56b9e7)));}var y=class{['_tickets']=new Map();['_disposed']=!0x1;async['issue'](_0x1c25b1,_0x3dd8e8){if(this['_disposed'])throw Error('Session\x20ticket\x20store\x20is\x20disposed');if(!Number['isSafeInteger'](_0x3dd8e8)||_0x3dd8e8<=0x0)throw TypeError('Session\x20ticket\x20TTL\x20must\x20be\x20a\x20positive\x20safe\x20integer');this['_purgeExpired']();let _0x3f4170;do _0x3f4170=(0x0,e['randomBytes'])(0x20)['toString']('base64url');while(this['_tickets']['has'](_0x3f4170));return this['_tickets']['set'](_0x3f4170,{'record':_0x1c25b1,'expiresAt':Date['now']()+_0x3dd8e8}),_0x3f4170;}async['consume'](_0x5f2e19){if(this['_disposed']||!_0x5f2e19)return null;let _0x40d176=this['_tickets']['get'](_0x5f2e19);return!_0x40d176||(this['_tickets']['delete'](_0x5f2e19),_0x40d176['expiresAt']<=Date['now']())?null:_0x40d176['record'];}async['dispose'](){this['_disposed']||(this['_disposed']=!0x0,this['_tickets']['clear']());}['_purgeExpired'](){let _0x576615=Date['now']();for(let [_0x4b4faa,_0x3dd4fa]of this['_tickets'])_0x3dd4fa['expiresAt']<=_0x576615&&this['_tickets']['delete'](_0x4b4faa);}},b=0x5*0xea60;function x(_0xa83720){if(!_0xa83720['startsWith']('/')||_0xa83720==='/'||_0xa83720['endsWith']('/')||_0xa83720['includes']('?')||_0xa83720['includes']('#'))throw TypeError('protocolBasePath\x20must\x20be\x20an\x20absolute\x20path\x20without\x20a\x20trailing\x20slash');return _0xa83720;}function S(_0x31921f){if(!_0x31921f||typeof _0x31921f!='object')throw new t['CollabError']('INVALID_REQUEST','Request\x20body\x20must\x20be\x20an\x20object');let _0x461a90=_0x31921f;if(typeof _0x461a90['unitID']!='string'||typeof _0x461a90['memberID']!='string'||!Number['isSafeInteger'](_0x461a90['type'])||!_0x461a90['changeset']||typeof _0x461a90['changeset']!='object')throw new t['CollabError']('INVALID_REQUEST','Invalid\x20new\x20changes\x20request');}function C(_0x16f2c9){if(!Array['isArray'](_0x16f2c9)||_0x16f2c9['length']===0x0||_0x16f2c9['some'](_0x51d6dc=>typeof _0x51d6dc!='string'||_0x51d6dc['trim']()['length']===0x0))throw new t['CollabError']('INVALID_REQUEST','unitIds\x20must\x20be\x20a\x20non-empty\x20string\x20array');}function w(_0x320714){let _0x3d2680=_0x320714['getAll']('unitIds');C(_0x3d2680);let _0x1806e7=_0x320714['getAll']('hardDelete');if(_0x1806e7['length']>0x1||_0x1806e7['length']===0x1&&_0x1806e7[0x0]!=='true'&&_0x1806e7[0x0]!=='false')throw new t['CollabError']('INVALID_REQUEST','hardDelete\x20must\x20be\x20true\x20or\x20false');return{'unitIds':_0x3d2680,'hardDelete':_0x1806e7[0x0]==='true'};}function T(_0x560a65){if(!_0x560a65||typeof _0x560a65!='object')throw new t['CollabError']('INVALID_REQUEST','Request\x20body\x20must\x20be\x20an\x20object');C(_0x560a65['unitIds']);}var E=class{['_service'];['_middleware']=new m();['_listeners']=new Map();['_bindingsByConnection']=new Map();['_bindingsByMember']=new Map();['_rooms']=new Map();['_outboundQueues']=new Map();['_ticketStore'];['_ownsTicketStore'];['_ticketTtlMs'];['_protocolBasePath'];['_serviceSubscriptions'];['_deletedUnitIDs']=new Set();['_disposed']=!0x1;constructor(_0x5d61d2,_0x5789a7={}){if(this['_service']=_0x5d61d2,!_0x5d61d2)throw TypeError('UniverCollabEndpoint\x20requires\x20a\x20service');if(this['_ticketStore']=_0x5789a7['ticketStore']??new y(),this['_ownsTicketStore']=_0x5789a7['ticketStore']===void 0x0,this['_ticketTtlMs']=_0x5789a7['sessionTicketTtlMs']??b,!Number['isSafeInteger'](this['_ticketTtlMs'])||this['_ticketTtlMs']<=0x0)throw TypeError('sessionTicketTtlMs\x20must\x20be\x20a\x20positive\x20safe\x20integer');this['_protocolBasePath']=x(_0x5789a7['protocolBasePath']??'/universer-api'),this['_serviceSubscriptions']=[this['_service']['on']('changesetCommitted',_0x31fc5d=>{this['_enqueueCommittedChangeset'](_0x31fc5d['changeset'],_0x31fc5d['memberID']);}),this['_service']['on']('unitsDeleted',_0x563c93=>{for(let _0x4125d6 of _0x563c93['request']['unitIDs'])this['_deletedUnitIDs']['add'](_0x4125d6);for(let _0x39631c of _0x563c93['request']['unitIDs'])this['_enqueueUnitOutbound'](_0x39631c,async()=>{this['_evictUnitRoom'](_0x39631c);});}),this['_service']['on']('unitsRecovered',async _0x10c727=>{await Promise['all'](_0x10c727['request']['unitIDs']['map'](_0xa78841=>this['_enqueueUnitOutbound'](_0xa78841,async()=>{this['_deletedUnitIDs']['delete'](_0xa78841);})));})];}['use'](_0x4547cb,_0x29dff6){return this['_assertRunning'](),this['_middleware']['use'](_0x4547cb,_0x29dff6);}['on'](_0x35943f,_0x2b3c68){this['_assertRunning']();let _0xf06b1e=this['_listeners']['get'](_0x35943f)??new Set(),_0x460e22=_0x2b3c68;_0xf06b1e['add'](_0x460e22),this['_listeners']['set'](_0x35943f,_0xf06b1e);let _0x2e8e24=!0x1;return{'dispose':()=>{_0x2e8e24||(_0x2e8e24=!0x0,_0xf06b1e['delete'](_0x460e22));}};}['findJoinedSession'](_0xc1c898){if(this['_disposed'])return null;let _0x346e0e=this['_bindingsByMember']['get'](_0xc1c898['memberID']);return!_0x346e0e||_0x346e0e['session']['userID']!==_0xc1c898['authenticatedUserID']||!_0x346e0e['joinedUnitIDs']['has'](_0xc1c898['unitID'])?null:_0x346e0e['session'];}['findJoinedSessionByUser'](_0x2aed05){if(this['_disposed'])return null;for(let _0x139e16 of this['_rooms']['get'](_0x2aed05['unitID'])?.['values']()??[])if(_0x139e16['session']['userID']===_0x2aed05['authenticatedUserID'])return _0x139e16['session'];return null;}async['invalidateUnitSessions'](_0x4193a0){if(this['_assertRunning'](),typeof _0x4193a0['unitID']!='string'||!_0x4193a0['unitID'])throw TypeError('unitID\x20must\x20be\x20a\x20non-empty\x20string');if(_0x4193a0['userID']!==void 0x0&&(typeof _0x4193a0['userID']!='string'||!_0x4193a0['userID']))throw TypeError('userID\x20must\x20be\x20a\x20non-empty\x20string\x20when\x20provided');await this['_enqueueUnitOutbound'](_0x4193a0['unitID'],async()=>{let _0x2c62fe=[...this['_rooms']['get'](_0x4193a0['unitID'])?.['values']()??[]]['filter'](_0xb7039=>_0x4193a0['userID']===void 0x0||_0xb7039['session']['userID']===_0x4193a0['userID']);for(let _0x2413f1 of _0x2c62fe)_0x2413f1['connection']['close'](0xfa3,'Unit\x20access\x20changed'),await this['_disconnectBinding'](_0x2413f1,'session-evicted');});}async['publishTrustedUnitEvent'](_0xd06b27){this['_assertRunning']();let _0x7ca261=[...this['_rooms']['get'](_0xd06b27['unitID'])?.['keys']()??[]],_0x2c5d06=this['_bindingsByMember']['get'](_0xd06b27['memberID'])?.['joinedUnitIDs']['has'](_0xd06b27['unitID'])?_0xd06b27['memberID']:void 0x0,_0x529018=p(_0xd06b27['unitID'],_0xd06b27['eventID'],_0xd06b27['data']);this['_enqueueUnitOutbound'](_0xd06b27['unitID'],async()=>{for(let _0x174a40 of _0x7ca261){if(_0x174a40===_0x2c5d06)continue;let _0x63b3e5=this['_bindingsByMember']['get'](_0x174a40);_0x63b3e5?.['joinedUnitIDs']['has'](_0xd06b27['unitID'])&&await this['_safeSend'](_0x63b3e5['connection'],_0x529018);}});}['register'](_0x189358){this['_assertRunning']();let _0x9266b1=this['_protocolBasePath'],_0x5012e0=_0x502909=>_0x52c476=>this['_handleHttp'](_0x52c476,_0x502909);_0x189358['get'](_0x9266b1+'/user/session-ticket',_0x5012e0('ticket')),_0x189358['delete'](_0x9266b1+'/snapshot/-/units',_0x5012e0('delete-units')),_0x189358['post'](_0x9266b1+'/snapshot/-/units/recover',_0x5012e0('recover-units')),_0x189358['post'](_0x9266b1+'/comb/:type/unit/:unitID/new_changes',_0x5012e0('new-changes')),_0x189358['get'](_0x9266b1+'/snapshot/block/:type/unit/:unitID/block/:blockID',_0x5012e0('deserialized-block')),_0x189358['get'](_0x9266b1+'/snapshot/:type/unit/:unitID/rev/:revision',_0x5012e0('unit')),_0x189358['get'](_0x9266b1+'/snapshot/:type/unit/:unitID/block/:blockID',_0x5012e0('block')),_0x189358['get'](_0x9266b1+'/snapshot/:type/unit/:unitID/fetchmissing',_0x5012e0('fetch-missing')),_0x189358['upgrade'](_0x9266b1+'/comb/connect',_0x13a4eb=>this['_handleUpgrade'](_0x13a4eb));}async['_handleUpgrade'](_0x15b911){if(this['_disposed']){_0x15b911['reject'](0x1f7,'Endpoint\x20is\x20disposed');return;}let _0x3cc7ad=new URL(_0x15b911['incomingMessage']['url']??'/','http://localhost'),_0x1db683=await this['_ticketStore']['consume'](_0x3cc7ad['searchParams']['get']('sessionTicket')??'');if(!_0x1db683){_0x15b911['reject'](0x191,'Invalid\x20or\x20expired\x20session\x20ticket');return;}_0x15b911['accept']({'open':({connection:_0x340aaf})=>this['_openConnection'](_0x1db683,_0x340aaf),'message':_0x5f14d0=>this['_handleMessage'](_0x5f14d0),'close':({connection:_0xf02477})=>this['_disconnectConnection'](_0xf02477,'connection-closed')});}async['dispose'](){if(this['_disposed'])return;this['_disposed']=!0x0;for(let _0x2ee950 of this['_serviceSubscriptions'])_0x2ee950['dispose']();await Promise['allSettled']([...this['_outboundQueues']['values']()]);let _0x1ada70=[...this['_bindingsByConnection']['values']()];for(let _0x2660aa of _0x1ada70)await this['_disconnectBinding'](_0x2660aa,'endpoint-disposed'),_0x2660aa['connection']['close'](0x3e9,'Endpoint\x20disposed');this['_bindingsByConnection']['clear'](),this['_bindingsByMember']['clear'](),this['_rooms']['clear'](),this['_outboundQueues']['clear'](),this['_middleware']['clear'](),this['_listeners']['clear'](),this['_ownsTicketStore']&&await this['_ticketStore']['dispose']?.();}async['_handleHttp'](_0x1a6e39,_0x3e744b){try{this['_assertRunning']();let _0x47d33d=new URL(_0x1a6e39['incomingMessage']['url']??'/','http://localhost');if(!_0x1a6e39['userID'])throw new t['CollabError']('UNAUTHENTICATED','HTTP\x20user\x20is\x20not\x20authenticated');if(_0x3e744b==='ticket'){let _0x4926a3=await this['_ticketStore']['issue']({'userID':_0x1a6e39['userID'],'customData':_0x1a6e39['customData']},this['_ticketTtlMs']);v(_0x1a6e39['response'],0xc8,{'error':h,'ticket':_0x4926a3});return;}let _0x34eae0={'userID':_0x1a6e39['userID'],'customData':_0x1a6e39['customData']};if(_0x3e744b==='delete-units'){let _0x1209e5=w(_0x47d33d['searchParams']);await this['_service']['deleteUnits']({'unitIDs':_0x1209e5['unitIds'],'hardDelete':_0x1209e5['hardDelete']},_0x34eae0),v(_0x1a6e39['response'],0xc8,{'error':h});return;}if(_0x3e744b==='recover-units'){let _0x263e99=await _0x1a6e39['readJson']();T(_0x263e99),await this['_service']['recoverUnits']({'unitIDs':_0x263e99['unitIds']},_0x34eae0),v(_0x1a6e39['response'],0xc8,{'error':h});return;}let _0x338457=g(_0x1a6e39['params']['type'],'type'),_0x574d2=_0x1a6e39['params']['unitID'];if(_0x3e744b==='new-changes'){await this['_handleNewChanges'](_0x1a6e39,_0x574d2,_0x338457);return;}switch(_0x3e744b){case'unit':{let _0x3db0d5=g(_0x1a6e39['params']['revision'],'revision'),_0x3b7719=await this['_service']['getUnitLoadData']({'unitID':_0x574d2,'type':_0x338457,'revision':_0x3db0d5},_0x34eae0);v(_0x1a6e39['response'],0xc8,{'error':h,'snapshot':_0x3b7719['snapshot'],'changesets':_0x3b7719['changesets']});return;}case'block':case'deserialized-block':{let _0x2bc931=await this['_service']['getSheetBlock']({'unitID':_0x574d2,'type':_0x338457,'blockID':_0x1a6e39['params']['blockID']},_0x34eae0);if(!_0x2bc931['block'])throw new t['CollabError']('UNIT_NOT_FOUND','Sheet\x20block\x20was\x20not\x20found');let _0x2ad549=_0x3e744b==='deserialized-block'?{..._0x2bc931['block'],'data':JSON['parse'](new TextDecoder()['decode'](_0x2bc931['block']['data']))}:_0x2bc931['block'];v(_0x1a6e39['response'],0xc8,{'error':h,'block':_0x2ad549});return;}case'fetch-missing':{let _0x58072b=g(_0x47d33d['searchParams']['get']('from')??'','from'),_0x383583=g(_0x47d33d['searchParams']['get']('to')??'','to'),_0x49fdf3=await this['_service']['getChangesets']({'unitID':_0x574d2,'type':_0x338457,'from':_0x58072b,'to':_0x383583},_0x34eae0);v(_0x1a6e39['response'],0xc8,{'error':h,'changesets':_0x49fdf3['changesets']});return;}}}catch(_0x194380){let _0x1ef186=_(_0x194380 instanceof SyntaxError?new t['CollabError']('INTERNAL_ERROR','Stored\x20sheet\x20block\x20is\x20not\x20valid\x20JSON',{'cause':_0x194380}):_0x194380);v(_0x1a6e39['response'],_0x1ef186['status'],{'error':_0x1ef186['error']});}}async['_openConnection'](_0xcb73df,_0x4f3026){try{let _0x435fae=(0x0,e['randomUUID'])(),_0x28ba7a={'memberID':_0x435fae,'userID':_0xcb73df['userID'],'customData':_0xcb73df['customData']},_0x24c10b={'memberID':_0x435fae,'userID':_0xcb73df['userID'],'name':_0xcb73df['userID']},_0x1c2fbb={'connection':_0x4f3026,'session':_0x28ba7a,'member':_0x24c10b};await this['_middleware']['run']('connect',_0x1c2fbb,()=>void 0x0);let _0x1d19d0={'connection':_0x4f3026,'session':_0x28ba7a,'member':_0x24c10b,'joinedUnitIDs':new Set()};this['_bindingsByConnection']['set'](_0x4f3026['id'],_0x1d19d0),this['_bindingsByMember']['set'](_0x435fae,_0x1d19d0);}catch(_0x3dac72){_0x4f3026['close'](_0x3dac72 instanceof t['CollabError']?0x3f0:0x3f3,_0x3dac72 instanceof t['CollabError']?'Connection\x20rejected':'Endpoint\x20failure');}}async['_handleMessage'](_0x1c5d2e){let _0x220889=this['_bindingsByConnection']['get'](_0x1c5d2e['connection']['id']);if(!_0x220889){_0x1c5d2e['connection']['close'](0x3f0,'WebSocket\x20Session\x20is\x20not\x20established');return;}if(typeof _0x1c5d2e['data']!='string'){_0x1c5d2e['connection']['close'](0x3eb,'Comb\x20messages\x20must\x20be\x20JSON\x20text');return;}let _0x145e9c;try{let _0x25b224=JSON['parse'](_0x1c5d2e['data']);if(!_0x25b224||typeof _0x25b224!='object')throw Error('invalid');_0x145e9c=_0x25b224;}catch{_0x1c5d2e['connection']['close'](0x3eb,'Invalid\x20Comb\x20JSON\x20message');return;}let _0x5ea736=typeof _0x145e9c['routeKey']=='string'?_0x145e9c['routeKey']:'';switch(_0x145e9c['cmd']){case n['CombCmd']['HELLO']:case n['CombCmd']['HEARTBEAT']:await _0x1c5d2e['connection']['send'](JSON['stringify'](a(_0x145e9c['cmd'],_0x220889['member']['memberID'],_0x5ea736)));return;case n['CombCmd']['JOIN']:await this['_handleJoin'](_0x220889,_0x145e9c,_0x5ea736);return;case n['CombCmd']['LEAVE']:await this['_handleLeave'](_0x220889,_0x145e9c,_0x5ea736);return;case n['CombCmd']['INGEST']:if(_0x145e9c['collaMsg']?.['eventID']!==i['UPDATE_CURSOR'])return;await this['_handlePresence'](_0x220889,_0x145e9c,_0x1c5d2e['customData']);return;default:return;}}async['_handleJoin'](_0x33fc33,_0x4e8e6a,_0x3508f5){let _0x252570=_0x4e8e6a['joinReq']?.['rooms'],_0x42d6d2=_0x252570?.['length']?_0x252570['map'](_0x47385e=>_0x47385e['roomID']):_0x3508f5?[_0x3508f5]:[];if(_0x42d6d2['length']===0x0||_0x42d6d2['some'](_0x127387=>typeof _0x127387!='string'||!_0x127387)){await _0x33fc33['connection']['send'](JSON['stringify'](o(_0x3508f5,n['CmdRspCode']['FAIL'],{},'Invalid\x20JOIN\x20request')));return;}let _0x21b96d=[...new Set(_0x42d6d2)],_0x29e21d=_0x21b96d['filter'](_0x38bbf0=>!_0x33fc33['joinedUnitIDs']['has'](_0x38bbf0));try{for(let _0x7b9899 of _0x21b96d)this['_assertJoinable'](_0x7b9899);for(let _0x4fe3a9 of _0x29e21d)await this['_middleware']['run']('joinUnit',{'session':_0x33fc33['session'],'unitID':_0x4fe3a9},()=>void 0x0);for(let _0xa8433a of _0x21b96d)this['_assertJoinable'](_0xa8433a);}catch(_0x2fa002){let _0x12c639=_0x2fa002 instanceof t['CollabError']&&_0x2fa002['code']==='UNIT_NOT_FOUND'?n['CmdRspCode']['JOIN_ROOM_NOT_EXISTS']:_0x2fa002 instanceof t['CollabError']&&(_0x2fa002['code']==='PERMISSION_DENIED'||_0x2fa002['code']==='UNAUTHENTICATED')?n['CmdRspCode']['JOIN_ROOM_PERMISSION_DENIED']:n['CmdRspCode']['FAIL'];await _0x33fc33['connection']['send'](JSON['stringify'](o(_0x3508f5||_0x21b96d[0x0],_0x12c639,{},_0x2fa002 instanceof t['CollabError']?_0x2fa002['message']:'JOIN\x20failed')));return;}for(let _0x77a20c of _0x29e21d){let _0x3a7dcb=this['_rooms']['get'](_0x77a20c)??new Map();_0x3a7dcb['set'](_0x33fc33['member']['memberID'],_0x33fc33),this['_rooms']['set'](_0x77a20c,_0x3a7dcb),_0x33fc33['joinedUnitIDs']['add'](_0x77a20c);}let _0x376adf={};for(let _0x435a88 of _0x21b96d)_0x376adf[_0x435a88]={'roomID':_0x435a88,'members':[...this['_rooms']['get'](_0x435a88)?.['values']()??[]]['map'](_0x963b2e=>s(_0x963b2e['member']))};await _0x33fc33['connection']['send'](JSON['stringify'](o(_0x3508f5||_0x21b96d[0x0],n['CmdRspCode']['OK'],_0x376adf,'success')));for(let _0x83ba25 of _0x29e21d)await this['_broadcastToRoom'](_0x83ba25,l(_0x83ba25,s(_0x33fc33['member'])),_0x33fc33['member']['memberID']);}async['_handleLeave'](_0x500a38,_0x5d40c9,_0x235808){let _0x28ed4b=typeof _0x5d40c9['leaveReq']?.['roomID']=='string'?_0x5d40c9['leaveReq']['roomID']:_0x235808;_0x28ed4b&&await this['_leaveUnit'](_0x500a38,_0x28ed4b,'client-leave');}async['_handlePresence'](_0x49ab8b,_0x2d58cf,_0x53bb57){let _0x32c054=typeof _0x2d58cf['routeKey']=='string'?_0x2d58cf['routeKey']:'',_0x1363d9=_0x2d58cf['collaMsg']?.['updateCursorEvent']?.['selection'];if(!_0x32c054||!_0x49ab8b['joinedUnitIDs']['has'](_0x32c054)||typeof _0x1363d9!='string')return;let _0x44a892={'unitID':_0x32c054,'memberID':_0x49ab8b['member']['memberID'],'selection':_0x1363d9};try{await this['_middleware']['run']('receivePresence',{'session':_0x49ab8b['session'],'unitID':_0x32c054,'payload':_0x44a892,'customData':_0x53bb57},()=>void 0x0);}catch(_0x5639a9){if(_0x5639a9 instanceof t['CollabError'])return;throw _0x5639a9;}if(typeof _0x44a892['selection']!='string')return;let _0x3fc2e5=[...this['_rooms']['get'](_0x32c054)?.['values']()??[]]['filter'](_0x3d3997=>_0x3d3997!==_0x49ab8b);for(let _0x203e04 of _0x3fc2e5){let _0x24af1e={'unitID':_0x32c054,'memberID':_0x49ab8b['member']['memberID'],'selection':_0x44a892['selection']};try{await this['_middleware']['run']('sendPresence',{'session':_0x49ab8b['session'],'unitID':_0x32c054,'payload':_0x24af1e,'customData':_0x53bb57,'targetMemberID':_0x203e04['member']['memberID']},()=>void 0x0);}catch{continue;}typeof _0x24af1e['selection']=='string'&&await this['_safeSend'](_0x203e04['connection'],d(_0x32c054,{'unitID':_0x32c054,'memberID':_0x49ab8b['member']['memberID'],'selection':_0x24af1e['selection']}));}}async['_handleNewChanges'](_0x4fcd18,_0x2ac2cd,_0x1463e9){let _0xb04559=await _0x4fcd18['readJson']();if(S(_0xb04559),_0xb04559['unitID']!==_0x2ac2cd||_0xb04559['type']!==_0x1463e9||_0xb04559['changeset']?.['unitID']!==_0x2ac2cd||_0xb04559['changeset']['type']!==_0x1463e9)throw new t['CollabError']('INVALID_REQUEST','Route,\x20request\x20and\x20changeset\x20identity\x20must\x20match');let _0x3e33e5=this['_bindingsByMember']['get'](_0xb04559['memberID']);if(!_0x3e33e5||_0x3e33e5['session']['userID']!==_0x4fcd18['userID'])throw new t['CollabError']('PERMISSION_DENIED','memberID\x20does\x20not\x20belong\x20to\x20the\x20authenticated\x20user');if(!_0x3e33e5['joinedUnitIDs']['has'](_0x2ac2cd))throw new t['CollabError']('PERMISSION_DENIED','Session\x20has\x20not\x20joined\x20this\x20unit');let _0x574035=await this['_service']['submitChangeset']({'changeset':_0xb04559['changeset']},{'userID':_0x4fcd18['userID'],'memberID':_0x3e33e5['session']['memberID'],'customData':_0x4fcd18['customData']});if(_0x574035['status']==='already-committed')await this['_safeSend'](_0x3e33e5['connection'],f(_0x2ac2cd,i['CHANGESET_ACK'],_0x574035['changeset']));else{if(_0x574035['status']==='rejected'){let _0x225e7f=_0x574035['error']['code']==='PERMISSION_DENIED'||_0x574035['error']['code']==='UNAUTHENTICATED'?i['PERMISSION_REJ']:i['CHANGESET_REJ'];await this['_safeSend'](_0x3e33e5['connection'],f(_0x2ac2cd,_0x225e7f,this['_trustedClientChangeset'](_0xb04559['changeset'],_0x3e33e5)));}else _0x574035['status']==='retry'&&await this['_safeSend'](_0x3e33e5['connection'],f(_0x2ac2cd,i['CHANGESET_SHOULD_RETRY'],this['_trustedClientChangeset'](_0xb04559['changeset'],_0x3e33e5)));}v(_0x4fcd18['response'],0xc8,{'error':h});}['_trustedClientChangeset'](_0x40b6f1,_0x14b829){return{..._0x40b6f1,'userID':_0x14b829['session']['userID'],'memberID':_0x14b829['session']['memberID']};}['_enqueueCommittedChangeset'](_0x1864fc,_0x3cf3be){if(this['_disposed'])return;let _0x212725=_0x1864fc['unitID'],_0x394b6c=[...this['_rooms']['get'](_0x212725)?.['keys']()??[]];this['_enqueueUnitOutbound'](_0x212725,async()=>{for(let _0x1dbd00 of _0x394b6c){let _0x554ec7=this['_bindingsByMember']['get'](_0x1dbd00);if(!_0x554ec7?.['joinedUnitIDs']['has'](_0x212725))continue;let _0x5425fd=_0x1dbd00===_0x3cf3be?i['CHANGESET_ACK']:i['NEW_CHANGESETS'];await this['_safeSend'](_0x554ec7['connection'],f(_0x212725,_0x5425fd,_0x1864fc));}});}['_evictUnitRoom'](_0x55676c){let _0x5a40ec=this['_rooms']['get'](_0x55676c);if(_0x5a40ec){this['_rooms']['delete'](_0x55676c);for(let _0x1f9a5f of _0x5a40ec['values']())_0x1f9a5f['joinedUnitIDs']['delete'](_0x55676c);}}['_enqueueUnitOutbound'](_0x3f9afc,_0x12b8a8){let _0x5aa25b=(this['_outboundQueues']['get'](_0x3f9afc)??Promise['resolve']())['catch'](()=>void 0x0)['then'](_0x12b8a8);return this['_outboundQueues']['set'](_0x3f9afc,_0x5aa25b),_0x5aa25b['finally'](()=>{this['_outboundQueues']['get'](_0x3f9afc)===_0x5aa25b&&this['_outboundQueues']['delete'](_0x3f9afc);}),_0x5aa25b;}async['_disconnectConnection'](_0x182dc2,_0x50965c){let _0xbc39bc=this['_bindingsByConnection']['get'](_0x182dc2['id']);_0xbc39bc&&await this['_disconnectBinding'](_0xbc39bc,_0x50965c);}async['_disconnectBinding'](_0x4ccf3c,_0x554404){this['_bindingsByConnection']['delete'](_0x4ccf3c['connection']['id']),this['_bindingsByMember']['delete'](_0x4ccf3c['member']['memberID']);let _0x203817=[..._0x4ccf3c['joinedUnitIDs']];for(let _0x5c035c of _0x203817)await this['_leaveUnit'](_0x4ccf3c,_0x5c035c,_0x554404);}async['_leaveUnit'](_0x27af92,_0x2c57b5,_0x9c6593){if(!_0x27af92['joinedUnitIDs']['delete'](_0x2c57b5))return!0x1;let _0x589100=this['_rooms']['get'](_0x2c57b5);return _0x589100?.['delete'](_0x27af92['member']['memberID']),_0x589100?.['size']===0x0&&this['_rooms']['delete'](_0x2c57b5),await this['_broadcastToRoom'](_0x2c57b5,u(_0x2c57b5,{'memberID':_0x27af92['member']['memberID'],'name':_0x27af92['member']['name']}),_0x27af92['member']['memberID']),await this['_emit']('memberLeftUnit',{'session':_0x27af92['session'],'unitID':_0x2c57b5,'reason':_0x9c6593}),!0x0;}async['_broadcastToRoom'](_0x515cc0,_0x170d1f,_0x4e2959){let _0x45b478=[...this['_rooms']['get'](_0x515cc0)?.['values']()??[]];await Promise['allSettled'](_0x45b478['filter'](_0x254379=>_0x254379['member']['memberID']!==_0x4e2959)['map'](_0x5a710d=>this['_safeSend'](_0x5a710d['connection'],_0x170d1f)));}async['_safeSend'](_0x331b1c,_0x311543){try{await _0x331b1c['send'](JSON['stringify'](_0x311543));}catch{}}async['_emit'](_0x6fd9e4,_0x38c738){let _0x5c9b54=[...this['_listeners']['get'](_0x6fd9e4)??[]];for(let _0xdca2a5 of _0x5c9b54)try{await _0xdca2a5(_0x38c738);}catch{}}['_assertRunning'](){if(this['_disposed'])throw Error('UniverCollabEndpoint\x20is\x20disposed');}['_assertJoinable'](_0x2c1c4f){if(this['_deletedUnitIDs']['has'](_0x2c1c4f))throw new t['CollabError']('UNIT_NOT_FOUND','Unit\x20has\x20been\x20deleted');}};exports['MemorySessionTicketStore']=y,exports['UniverCollabEndpoint']=E;
package/dist/index.d.ts CHANGED
@@ -1,14 +1,13 @@
1
- import { CollabSession, CustomData, Disposable, IUniverCollabService } from "@univerjs-pro/collaboration-service";
2
- import { NodeTransportConnection, NodeTransportContext, NodeTransportMiddlewareObject, NodeTransportNext } from "@univerjs-pro/collaboration-transport-node";
1
+ import { CustomData, Disposable, IUniverCollabService } from "@univerjs-pro/collaboration-service";
2
+ import { NodeRouter, NodeTransportConnection, NodeTransportEndpoint } from "@univerjs-pro/collaboration-transport-node";
3
3
 
4
4
  //#region src/endpoint.d.ts
5
5
  type EndpointListener<K extends keyof EndpointEventMap> = (event: EndpointEventMap[K]) => void | Promise<void>;
6
- declare class UniverCollabEndpoint implements NodeTransportMiddlewareObject, ITrustedUnitRoomHost {
6
+ declare class UniverCollabEndpoint implements NodeTransportEndpoint, ITrustedUnitRoomHost {
7
7
  private readonly _service;
8
8
  private readonly _middleware;
9
9
  private readonly _listeners;
10
10
  private readonly _bindingsByConnection;
11
- private readonly _claimedConnectionIDs;
12
11
  private readonly _bindingsByMember;
13
12
  private readonly _rooms;
14
13
  private readonly _outboundQueues;
@@ -16,32 +15,33 @@ declare class UniverCollabEndpoint implements NodeTransportMiddlewareObject, ITr
16
15
  private readonly _ownsTicketStore;
17
16
  private readonly _ticketTtlMs;
18
17
  private readonly _protocolBasePath;
19
- private readonly _combPath;
20
18
  private readonly _serviceSubscriptions;
21
19
  private readonly _deletedUnitIDs;
22
20
  private _disposed;
23
- constructor(_service: IUniverCollabService, options?: UniverCollabEndpointOptions);
21
+ constructor(_service: Omit<IUniverCollabService, "dbAdapter">, options?: UniverCollabEndpointOptions);
24
22
  use<K extends EndpointMiddlewareAction>(action: K, middleware: EndpointMiddlewareMap[K]): Disposable;
25
23
  on<K extends keyof EndpointEventMap>(event: K, listener: EndpointListener<K>): Disposable;
26
24
  findJoinedSession(input: {
27
25
  readonly unitID: string;
28
26
  readonly memberID: string;
29
27
  readonly authenticatedUserID: string;
30
- }): CollabSession | null;
28
+ }): EndpointSession | null;
31
29
  findJoinedSessionByUser(input: {
32
30
  readonly unitID: string;
33
31
  readonly authenticatedUserID: string;
34
- }): CollabSession | null;
32
+ }): EndpointSession | null;
33
+ invalidateUnitSessions(input: InvalidateUnitSessionsInput): Promise<void>;
35
34
  publishTrustedUnitEvent(input: {
36
35
  readonly unitID: string;
37
- readonly origin: CollabSession;
36
+ readonly memberID: string;
38
37
  readonly eventID: string;
39
38
  readonly data: Readonly<Record<string, unknown>>;
40
39
  }): Promise<void>;
41
- handle(ctx: NodeTransportContext, next: NodeTransportNext): Promise<void>;
40
+ register(router: NodeRouter): void;
41
+ private _handleUpgrade;
42
42
  dispose(): Promise<void>;
43
43
  private _handleHttp;
44
- private _handleOpen;
44
+ private _openConnection;
45
45
  private _handleMessage;
46
46
  private _handleJoin;
47
47
  private _handleLeave;
@@ -73,7 +73,12 @@ declare class MemorySessionTicketStore implements ISessionTicketStore {
73
73
  //#endregion
74
74
  //#region src/univer-collab-endpoint.d.ts
75
75
  interface SessionTicketRecord {
76
- readonly userId: string;
76
+ readonly userID: string;
77
+ readonly customData: CustomData;
78
+ }
79
+ interface EndpointSession {
80
+ readonly userID: string;
81
+ readonly memberID: string;
77
82
  readonly customData: CustomData;
78
83
  }
79
84
  interface ISessionTicketStore {
@@ -83,7 +88,7 @@ interface ISessionTicketStore {
83
88
  }
84
89
  interface EndpointSessionBinding {
85
90
  readonly connection: NodeTransportConnection;
86
- readonly session: CollabSession;
91
+ readonly session: EndpointSession;
87
92
  readonly member: EndpointMember;
88
93
  readonly joinedUnitIDs: ReadonlySet<string>;
89
94
  }
@@ -92,14 +97,14 @@ interface ITrustedUnitRoomHost {
92
97
  readonly unitID: string;
93
98
  readonly memberID: string;
94
99
  readonly authenticatedUserID: string;
95
- }): CollabSession | null;
100
+ }): EndpointSession | null;
96
101
  findJoinedSessionByUser(input: {
97
102
  readonly unitID: string;
98
103
  readonly authenticatedUserID: string;
99
- }): CollabSession | null;
104
+ }): EndpointSession | null;
100
105
  publishTrustedUnitEvent(input: {
101
106
  readonly unitID: string;
102
- readonly origin: CollabSession;
107
+ readonly memberID: string;
103
108
  readonly eventID: string;
104
109
  readonly data: Readonly<Record<string, unknown>>;
105
110
  }): Promise<void>;
@@ -111,7 +116,7 @@ interface EndpointMember {
111
116
  avatar?: string;
112
117
  }
113
118
  interface BaseEndpointMiddlewareContext {
114
- readonly session: CollabSession;
119
+ readonly session: EndpointSession;
115
120
  }
116
121
  interface ConnectEndpointMiddlewareContext extends BaseEndpointMiddlewareContext {
117
122
  readonly connection: NodeTransportConnection;
@@ -126,7 +131,7 @@ interface ReceivePresenceEndpointMiddlewareContext extends BaseEndpointMiddlewar
126
131
  readonly customData: CustomData;
127
132
  }
128
133
  interface SendPresenceEndpointMiddlewareContext extends ReceivePresenceEndpointMiddlewareContext {
129
- readonly targetMemberId: string;
134
+ readonly targetMemberID: string;
130
135
  }
131
136
  type EndpointMiddlewareAction = "connect" | "joinUnit" | "receivePresence" | "sendPresence";
132
137
  type EndpointMiddlewareNext = () => Promise<void>;
@@ -137,10 +142,15 @@ interface EndpointMiddlewareMap {
137
142
  readonly receivePresence: EndpointMiddleware<ReceivePresenceEndpointMiddlewareContext>;
138
143
  readonly sendPresence: EndpointMiddleware<SendPresenceEndpointMiddlewareContext>;
139
144
  }
145
+ type MemberLeftUnitReason = "client-leave" | "connection-closed" | "endpoint-disposed" | "session-evicted";
140
146
  interface MemberLeftUnitEvent {
141
- readonly session: CollabSession;
147
+ readonly session: EndpointSession;
148
+ readonly unitID: string;
149
+ readonly reason: MemberLeftUnitReason;
150
+ }
151
+ interface InvalidateUnitSessionsInput {
142
152
  readonly unitID: string;
143
- readonly reason: "explicit" | "disconnect" | "endpoint-dispose";
153
+ readonly userID?: string;
144
154
  }
145
155
  interface EndpointEventMap {
146
156
  readonly memberLeftUnit: MemberLeftUnitEvent;
@@ -151,4 +161,4 @@ interface UniverCollabEndpointOptions {
151
161
  readonly protocolBasePath?: string;
152
162
  }
153
163
  //#endregion
154
- export { type BaseEndpointMiddlewareContext, type ConnectEndpointMiddlewareContext, type EndpointEventMap, type EndpointMember, type EndpointMiddleware, type EndpointMiddlewareAction, type EndpointMiddlewareMap, type EndpointMiddlewareNext, type EndpointSessionBinding, type ISessionTicketStore, type ITrustedUnitRoomHost, type JoinUnitEndpointMiddlewareContext, type MemberLeftUnitEvent, MemorySessionTicketStore, type ReceivePresenceEndpointMiddlewareContext, type SendPresenceEndpointMiddlewareContext, type SessionTicketRecord, UniverCollabEndpoint, type UniverCollabEndpointOptions };
164
+ export { type BaseEndpointMiddlewareContext, type ConnectEndpointMiddlewareContext, type EndpointEventMap, type EndpointMember, type EndpointMiddleware, type EndpointMiddlewareAction, type EndpointMiddlewareMap, type EndpointMiddlewareNext, type EndpointSession, type EndpointSessionBinding, type ISessionTicketStore, type ITrustedUnitRoomHost, type InvalidateUnitSessionsInput, type JoinUnitEndpointMiddlewareContext, type MemberLeftUnitEvent, type MemberLeftUnitReason, MemorySessionTicketStore, type ReceivePresenceEndpointMiddlewareContext, type SendPresenceEndpointMiddlewareContext, type SessionTicketRecord, UniverCollabEndpoint, type UniverCollabEndpointOptions };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- const _0x21dab9=_0x2578;(function(_0x160f4,_0x2d5d88){const _0x22f047=_0x2578,_0x53b096=_0x160f4();while(!![]){try{const _0x50dfaa=-parseInt(_0x22f047(0x1c3))/0x1+parseInt(_0x22f047(0x1c4))/0x2+parseInt(_0x22f047(0x1bc))/0x3+parseInt(_0x22f047(0x1c1))/0x4*(-parseInt(_0x22f047(0x1bd))/0x5)+parseInt(_0x22f047(0x1c0))/0x6+-parseInt(_0x22f047(0x1be))/0x7*(parseInt(_0x22f047(0x1bf))/0x8)+parseInt(_0x22f047(0x1c2))/0x9;if(_0x50dfaa===_0x2d5d88)break;else _0x53b096['push'](_0x53b096['shift']());}catch(_0x389100){_0x53b096['push'](_0x53b096['shift']());}}}(_0x2526,0x4e7a8));function _0x2526(){const _0x321fb1=['2723370jRGSPh','639228Xilkdn','8600247zhPzKc','533075UxSuZO','427526XGLieN','UniverCollabEndpoint','370104OUrTpX','10oNRbCk','55657dfwdUc','576eDHisY'];_0x2526=function(){return _0x321fb1;};return _0x2526();}import _0xcb0c55 from'./index.cjs';function _0x2578(_0x2becc8,_0x557305){_0x2becc8=_0x2becc8-0x1bc;const _0x25260b=_0x2526();let _0x257801=_0x25260b[_0x2becc8];return _0x257801;}const facadeExport0=_0xcb0c55['MemorySessionTicketStore'],facadeExport1=_0xcb0c55[_0x21dab9(0x1c5)];export{facadeExport0 as MemorySessionTicketStore,facadeExport1 as UniverCollabEndpoint};
1
+ const _0x18e651=_0x59d3;(function(_0x412547,_0x41f397){const _0x2116f3=_0x59d3,_0x8562a6=_0x412547();while(!![]){try{const _0x2d1cf2=-parseInt(_0x2116f3(0xc3))/0x1+parseInt(_0x2116f3(0xc9))/0x2*(-parseInt(_0x2116f3(0xc8))/0x3)+parseInt(_0x2116f3(0xbf))/0x4+parseInt(_0x2116f3(0xc2))/0x5*(parseInt(_0x2116f3(0xca))/0x6)+-parseInt(_0x2116f3(0xc4))/0x7*(parseInt(_0x2116f3(0xc7))/0x8)+parseInt(_0x2116f3(0xc5))/0x9+parseInt(_0x2116f3(0xc1))/0xa*(parseInt(_0x2116f3(0xc0))/0xb);if(_0x2d1cf2===_0x41f397)break;else _0x8562a6['push'](_0x8562a6['shift']());}catch(_0x2fffa9){_0x8562a6['push'](_0x8562a6['shift']());}}}(_0x2ff8,0xbaf87));function _0x2ff8(){const _0x9cb722=['30ATPPXR','935TyeEuk','275887fKfHhj','258986UqGzIW','4330836bZxCWf','MemorySessionTicketStore','96ligVNG','165VmVyZA','9678hIlrSv','34122tzXDnl','5728QonoDH','754358dRoeOY'];_0x2ff8=function(){return _0x9cb722;};return _0x2ff8();}import _0x3639f9 from'./index.cjs';const facadeExport0=_0x3639f9[_0x18e651(0xc6)],facadeExport1=_0x3639f9['UniverCollabEndpoint'];function _0x59d3(_0x42ae56,_0x4ca965){_0x42ae56=_0x42ae56-0xbf;const _0x2ff8cc=_0x2ff8();let _0x59d3c9=_0x2ff8cc[_0x42ae56];return _0x59d3c9;}export{facadeExport0 as MemorySessionTicketStore,facadeExport1 as UniverCollabEndpoint};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@univerjs-pro/collaboration-endpoint",
3
3
  "type": "module",
4
- "version": "1.0.0-insiders.20260808-70eefdd",
4
+ "version": "1.0.0",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -16,11 +16,8 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@univerjs-pro/collaboration-service": "1.0.0-insiders.20260808-70eefdd",
20
- "@univerjs-pro/collaboration-transport-node": "1.0.0-insiders.20260808-70eefdd",
21
- "@univerjs/protocol": "1.0.0-insiders.20260808-70eefdd"
22
- },
23
- "publishConfig": {
24
- "access": "public"
19
+ "@univerjs-pro/collaboration-service": "1.0.0",
20
+ "@univerjs-pro/collaboration-transport-node": "1.0.0",
21
+ "@univerjs/protocol": "1.0.0"
25
22
  }
26
- }
23
+ }