@myronsi/messenger-api 2.0.0-alpha.1.next.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/CHANGELOG.md +11 -0
- package/README.md +58 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/openapi.yaml +2334 -0
- package/dist/schema.d.ts +3889 -0
- package/dist/ws-docs.json +428 -0
- package/dist/ws-events.d.ts +526 -0
- package/dist/ws-events.schema.json +2739 -0
- package/package.json +54 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
{
|
|
2
|
+
"markdown": "# WebSocket protocol (API contract v2)\n\nThe JSON Schemas in `websocket/client/` (client → server) and `websocket/server/` (server → client) are the source of truth. Every event has an example in `websocket/examples/`, validated against its schema by `npm run check`. TypeScript types are generated into `dist/ws-events.d.ts` (`ClientEvent`, `ServerEvent`).\n\n## Connecting\n\n1. `POST /api/v2/ws/ticket` (authenticated) returns `{ \"ticket\": \"…\", \"expires_in\": 30 }`. The ticket is single-use and short-lived, so no access token ends up in a URL or a log.\n2. Open **one connection per user**: `wss://<host>/api/v2/ws?ticket=<ticket>`. The connection carries the events of all chats of the user.\n3. The first server event is `hello` with `api_version` and `min_client_api_version`. A client that reconnects after a deploy compares them with the contract version it was built for and shows an upgrade notice instead of failing at random.\n4. When the ticket is invalid or expired the server closes the connection with code `4401`. An outdated client is closed with `4426` after `hello`.\n\n## Envelope\n\nServer → client:\n\n```json\n{ \"type\": \"message\", \"event_id\": \"7217400317439950849\", \"chat_id\": \"42\", \"data\": { \"message\": { \"…\": \"…\" } } }\n```\n\nClient → server:\n\n```json\n{ \"type\": \"message\", \"client_temp_id\": \"c-1\", \"chat_id\": \"42\", \"data\": { \"type\": \"text\", \"content\": \"Hello!\" } }\n```\n\n- All IDs are strings (Snowflake IDs do not fit into a JavaScript number).\n- `event_id` is unique and increases per server; it is only for deduplication and logs.\n- `chat_id` is always `null` in `hello`, `presence` and `approval_request_created`, which do not belong to a chat. `ack` and `error` repeat the `chat_id` of the client event they answer, and it is `null` for connection-level errors.\n- Unknown event `type`s must be ignored by clients, so new event types are not a breaking change.\n\n## Acknowledgements\n\nEvery client event except `typing` (which is not acknowledged and carries no `client_temp_id`) has a `client_temp_id` (1–64 characters, unique per client). The server answers with exactly one of:\n\n- `ack` — `client_temp_id`, and for `message` the stored `message_id` and `created_at`. Sending the same `client_temp_id` again returns the same `ack` (idempotent), which makes retries after a reconnect safe.\n- `error` — `client_temp_id` and `data.code`/`data.message`. `code` is one of the stable codes of `ErrorCode` in `openapi.yaml` (the same codes as the REST `application/problem+json` errors).\n\nThe sender also receives the broadcast `message` event with the stored message (its `client_temp_id` is included, so the client can replace its pending copy).\n\n## Client → server events\n\n| Type | Purpose |\n| --- | --- |\n| `message` | Send a message (`type` `text`, `file` or `voice`; files are referenced by `attachment_id`, never by URL) |\n| `resend` | Deliver a stored but undelivered message again |\n| `edit` | Edit your own text message |\n| `delete` | Delete a message for yourself (`scope: me`) or everyone |\n| `read` | Mark messages as read up to `message_id` |\n| `reaction_add`, `reaction_remove` | Add or remove a reaction |\n| `typing` | Typing indicator (not acknowledged) |\n\n## Server → client events\n\n| Type | Meaning |\n| --- | --- |\n| `hello` | First event: `api_version`, `min_client_api_version`, `user_id` |\n| `ack`, `error` | Result of a client event, references `client_temp_id` |\n| `message`, `edit`, `delete` | Message created, edited or deleted |\n| `reaction_add`, `reaction_remove` | Reactions changed |\n| `read` | A user read up to `message_id` |\n| `typing` | A user is typing |\n| `chat_created`, `chat_deleted`, `chat_list_update` | Chat list changes |\n| `group_created`, `group_updated` | Group changes (members, roles, name, avatar) |\n| `approval_request_created` | New request in the inbox |\n| `presence` | A user came online or went offline |\n\n## Compatibility\n\nAdding an event type or an optional field is a MINOR change; removing or renaming an event or field, or making a field required, is MAJOR (see `docs/api-compatibility.md`). Clients must ignore unknown events and unknown fields.\n",
|
|
3
|
+
"examples": {
|
|
4
|
+
"client/delete.json": {
|
|
5
|
+
"type": "delete",
|
|
6
|
+
"client_temp_id": "c-1",
|
|
7
|
+
"chat_id": "42",
|
|
8
|
+
"data": {
|
|
9
|
+
"message_id": "7217400317439950001",
|
|
10
|
+
"scope": "everyone"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"client/edit.json": {
|
|
14
|
+
"type": "edit",
|
|
15
|
+
"client_temp_id": "c-1",
|
|
16
|
+
"chat_id": "42",
|
|
17
|
+
"data": {
|
|
18
|
+
"message_id": "7217400317439950001",
|
|
19
|
+
"content": "Hello, world!"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"client/message.json": {
|
|
23
|
+
"type": "message",
|
|
24
|
+
"client_temp_id": "c-1",
|
|
25
|
+
"chat_id": "42",
|
|
26
|
+
"data": {
|
|
27
|
+
"type": "text",
|
|
28
|
+
"content": "Hello!"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"client/reaction_add.json": {
|
|
32
|
+
"type": "reaction_add",
|
|
33
|
+
"client_temp_id": "c-1",
|
|
34
|
+
"chat_id": "42",
|
|
35
|
+
"data": {
|
|
36
|
+
"message_id": "7217400317439950001",
|
|
37
|
+
"emoji": "👍"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"client/reaction_remove.json": {
|
|
41
|
+
"type": "reaction_remove",
|
|
42
|
+
"client_temp_id": "c-1",
|
|
43
|
+
"chat_id": "42",
|
|
44
|
+
"data": {
|
|
45
|
+
"message_id": "7217400317439950001",
|
|
46
|
+
"emoji": "👍"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"client/read.json": {
|
|
50
|
+
"type": "read",
|
|
51
|
+
"client_temp_id": "c-1",
|
|
52
|
+
"chat_id": "42",
|
|
53
|
+
"data": {
|
|
54
|
+
"message_id": "7217400317439950001"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"client/resend.json": {
|
|
58
|
+
"type": "resend",
|
|
59
|
+
"client_temp_id": "c-1",
|
|
60
|
+
"chat_id": "42",
|
|
61
|
+
"data": {
|
|
62
|
+
"message_id": "7217400317439950001"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"client/typing.json": {
|
|
66
|
+
"type": "typing",
|
|
67
|
+
"chat_id": "42",
|
|
68
|
+
"data": {
|
|
69
|
+
"is_typing": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"server/ack.json": {
|
|
73
|
+
"type": "ack",
|
|
74
|
+
"event_id": "7217400317439950009",
|
|
75
|
+
"chat_id": "42",
|
|
76
|
+
"data": {
|
|
77
|
+
"message_id": "7217400317439950001",
|
|
78
|
+
"created_at": "2026-10-04T12:00:00Z"
|
|
79
|
+
},
|
|
80
|
+
"client_temp_id": "c-1"
|
|
81
|
+
},
|
|
82
|
+
"server/approval_request_created.json": {
|
|
83
|
+
"type": "approval_request_created",
|
|
84
|
+
"event_id": "7217400317439950009",
|
|
85
|
+
"chat_id": null,
|
|
86
|
+
"data": {
|
|
87
|
+
"request": {
|
|
88
|
+
"id": "5",
|
|
89
|
+
"type": "direct_message",
|
|
90
|
+
"status": "pending",
|
|
91
|
+
"requester": {
|
|
92
|
+
"id": "1001",
|
|
93
|
+
"username": "alice",
|
|
94
|
+
"display_name": "Alice",
|
|
95
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
96
|
+
"bio": null,
|
|
97
|
+
"is_online": true,
|
|
98
|
+
"last_seen": null,
|
|
99
|
+
"is_deleted": false
|
|
100
|
+
},
|
|
101
|
+
"group_name": null,
|
|
102
|
+
"preview": "Hi, can we talk?",
|
|
103
|
+
"created_at": "2026-10-04T12:00:00Z"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"server/chat_created.json": {
|
|
108
|
+
"type": "chat_created",
|
|
109
|
+
"event_id": "7217400317439950009",
|
|
110
|
+
"chat_id": "42",
|
|
111
|
+
"data": {
|
|
112
|
+
"chat": {
|
|
113
|
+
"id": "42",
|
|
114
|
+
"type": "direct",
|
|
115
|
+
"name": "Alice",
|
|
116
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
117
|
+
"peer": {
|
|
118
|
+
"id": "1001",
|
|
119
|
+
"username": "alice",
|
|
120
|
+
"display_name": "Alice",
|
|
121
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
122
|
+
"bio": null,
|
|
123
|
+
"is_online": true,
|
|
124
|
+
"last_seen": null,
|
|
125
|
+
"is_deleted": false
|
|
126
|
+
},
|
|
127
|
+
"is_pinned": false,
|
|
128
|
+
"unread_count": 1,
|
|
129
|
+
"last_message": {
|
|
130
|
+
"id": "7217400317439950001",
|
|
131
|
+
"chat_id": "42",
|
|
132
|
+
"type": "text",
|
|
133
|
+
"sender": {
|
|
134
|
+
"id": "1001",
|
|
135
|
+
"username": "alice",
|
|
136
|
+
"display_name": "Alice",
|
|
137
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
138
|
+
"bio": null,
|
|
139
|
+
"is_online": true,
|
|
140
|
+
"last_seen": null,
|
|
141
|
+
"is_deleted": false
|
|
142
|
+
},
|
|
143
|
+
"content": "Hello!",
|
|
144
|
+
"attachment": null,
|
|
145
|
+
"reply_to": null,
|
|
146
|
+
"forwarded_from": null,
|
|
147
|
+
"reactions": [],
|
|
148
|
+
"read_by": [],
|
|
149
|
+
"created_at": "2026-10-04T12:00:00Z",
|
|
150
|
+
"edited_at": null,
|
|
151
|
+
"is_deleted": false,
|
|
152
|
+
"client_temp_id": "c-1"
|
|
153
|
+
},
|
|
154
|
+
"my_role": null,
|
|
155
|
+
"created_at": "2026-10-04T12:00:00Z"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"server/chat_deleted.json": {
|
|
160
|
+
"type": "chat_deleted",
|
|
161
|
+
"event_id": "7217400317439950009",
|
|
162
|
+
"chat_id": "42",
|
|
163
|
+
"data": {}
|
|
164
|
+
},
|
|
165
|
+
"server/chat_list_update.json": {
|
|
166
|
+
"type": "chat_list_update",
|
|
167
|
+
"event_id": "7217400317439950009",
|
|
168
|
+
"chat_id": "42",
|
|
169
|
+
"data": {
|
|
170
|
+
"chat": {
|
|
171
|
+
"id": "42",
|
|
172
|
+
"type": "direct",
|
|
173
|
+
"name": "Alice",
|
|
174
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
175
|
+
"peer": {
|
|
176
|
+
"id": "1001",
|
|
177
|
+
"username": "alice",
|
|
178
|
+
"display_name": "Alice",
|
|
179
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
180
|
+
"bio": null,
|
|
181
|
+
"is_online": true,
|
|
182
|
+
"last_seen": null,
|
|
183
|
+
"is_deleted": false
|
|
184
|
+
},
|
|
185
|
+
"is_pinned": false,
|
|
186
|
+
"unread_count": 1,
|
|
187
|
+
"last_message": {
|
|
188
|
+
"id": "7217400317439950001",
|
|
189
|
+
"chat_id": "42",
|
|
190
|
+
"type": "text",
|
|
191
|
+
"sender": {
|
|
192
|
+
"id": "1001",
|
|
193
|
+
"username": "alice",
|
|
194
|
+
"display_name": "Alice",
|
|
195
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
196
|
+
"bio": null,
|
|
197
|
+
"is_online": true,
|
|
198
|
+
"last_seen": null,
|
|
199
|
+
"is_deleted": false
|
|
200
|
+
},
|
|
201
|
+
"content": "Hello!",
|
|
202
|
+
"attachment": null,
|
|
203
|
+
"reply_to": null,
|
|
204
|
+
"forwarded_from": null,
|
|
205
|
+
"reactions": [],
|
|
206
|
+
"read_by": [],
|
|
207
|
+
"created_at": "2026-10-04T12:00:00Z",
|
|
208
|
+
"edited_at": null,
|
|
209
|
+
"is_deleted": false,
|
|
210
|
+
"client_temp_id": "c-1"
|
|
211
|
+
},
|
|
212
|
+
"my_role": null,
|
|
213
|
+
"created_at": "2026-10-04T12:00:00Z"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"server/delete.json": {
|
|
218
|
+
"type": "delete",
|
|
219
|
+
"event_id": "7217400317439950009",
|
|
220
|
+
"chat_id": "42",
|
|
221
|
+
"data": {
|
|
222
|
+
"message_id": "7217400317439950001",
|
|
223
|
+
"scope": "everyone"
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
"server/edit.json": {
|
|
227
|
+
"type": "edit",
|
|
228
|
+
"event_id": "7217400317439950009",
|
|
229
|
+
"chat_id": "42",
|
|
230
|
+
"data": {
|
|
231
|
+
"message": {
|
|
232
|
+
"id": "7217400317439950001",
|
|
233
|
+
"chat_id": "42",
|
|
234
|
+
"type": "text",
|
|
235
|
+
"sender": {
|
|
236
|
+
"id": "1001",
|
|
237
|
+
"username": "alice",
|
|
238
|
+
"display_name": "Alice",
|
|
239
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
240
|
+
"bio": null,
|
|
241
|
+
"is_online": true,
|
|
242
|
+
"last_seen": null,
|
|
243
|
+
"is_deleted": false
|
|
244
|
+
},
|
|
245
|
+
"content": "Hello, world!",
|
|
246
|
+
"attachment": null,
|
|
247
|
+
"reply_to": null,
|
|
248
|
+
"forwarded_from": null,
|
|
249
|
+
"reactions": [],
|
|
250
|
+
"read_by": [],
|
|
251
|
+
"created_at": "2026-10-04T12:00:00Z",
|
|
252
|
+
"edited_at": "2026-10-04T12:00:00Z",
|
|
253
|
+
"is_deleted": false,
|
|
254
|
+
"client_temp_id": "c-1"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"server/error.json": {
|
|
259
|
+
"type": "error",
|
|
260
|
+
"event_id": "7217400317439950009",
|
|
261
|
+
"chat_id": "42",
|
|
262
|
+
"data": {
|
|
263
|
+
"code": "forbidden",
|
|
264
|
+
"message": "You are not a member of this chat"
|
|
265
|
+
},
|
|
266
|
+
"client_temp_id": "c-1"
|
|
267
|
+
},
|
|
268
|
+
"server/group_created.json": {
|
|
269
|
+
"type": "group_created",
|
|
270
|
+
"event_id": "7217400317439950009",
|
|
271
|
+
"chat_id": "77",
|
|
272
|
+
"data": {
|
|
273
|
+
"group": {
|
|
274
|
+
"id": "77",
|
|
275
|
+
"name": "Team",
|
|
276
|
+
"description": null,
|
|
277
|
+
"avatar_url": null,
|
|
278
|
+
"owner_id": "1001",
|
|
279
|
+
"my_role": "owner",
|
|
280
|
+
"members": [
|
|
281
|
+
{
|
|
282
|
+
"user": {
|
|
283
|
+
"id": "1001",
|
|
284
|
+
"username": "alice",
|
|
285
|
+
"display_name": "Alice",
|
|
286
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
287
|
+
"bio": null,
|
|
288
|
+
"is_online": true,
|
|
289
|
+
"last_seen": null,
|
|
290
|
+
"is_deleted": false
|
|
291
|
+
},
|
|
292
|
+
"role": "owner",
|
|
293
|
+
"joined_at": "2026-10-04T12:00:00Z"
|
|
294
|
+
}
|
|
295
|
+
],
|
|
296
|
+
"created_at": "2026-10-04T12:00:00Z"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"server/group_updated.json": {
|
|
301
|
+
"type": "group_updated",
|
|
302
|
+
"event_id": "7217400317439950009",
|
|
303
|
+
"chat_id": "77",
|
|
304
|
+
"data": {
|
|
305
|
+
"group": {
|
|
306
|
+
"id": "77",
|
|
307
|
+
"name": "Team",
|
|
308
|
+
"description": null,
|
|
309
|
+
"avatar_url": null,
|
|
310
|
+
"owner_id": "1001",
|
|
311
|
+
"my_role": "owner",
|
|
312
|
+
"members": [
|
|
313
|
+
{
|
|
314
|
+
"user": {
|
|
315
|
+
"id": "1001",
|
|
316
|
+
"username": "alice",
|
|
317
|
+
"display_name": "Alice",
|
|
318
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
319
|
+
"bio": null,
|
|
320
|
+
"is_online": true,
|
|
321
|
+
"last_seen": null,
|
|
322
|
+
"is_deleted": false
|
|
323
|
+
},
|
|
324
|
+
"role": "owner",
|
|
325
|
+
"joined_at": "2026-10-04T12:00:00Z"
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
"created_at": "2026-10-04T12:00:00Z"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
"server/hello.json": {
|
|
333
|
+
"type": "hello",
|
|
334
|
+
"event_id": "7217400317439950009",
|
|
335
|
+
"chat_id": null,
|
|
336
|
+
"data": {
|
|
337
|
+
"api_version": "2.0.0-alpha.1",
|
|
338
|
+
"min_client_api_version": "2.0.0-alpha.1",
|
|
339
|
+
"user_id": "1001"
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"server/message.json": {
|
|
343
|
+
"type": "message",
|
|
344
|
+
"event_id": "7217400317439950009",
|
|
345
|
+
"chat_id": "42",
|
|
346
|
+
"data": {
|
|
347
|
+
"message": {
|
|
348
|
+
"id": "7217400317439950001",
|
|
349
|
+
"chat_id": "42",
|
|
350
|
+
"type": "text",
|
|
351
|
+
"sender": {
|
|
352
|
+
"id": "1001",
|
|
353
|
+
"username": "alice",
|
|
354
|
+
"display_name": "Alice",
|
|
355
|
+
"avatar_url": "/api/v2/users/1001/avatar",
|
|
356
|
+
"bio": null,
|
|
357
|
+
"is_online": true,
|
|
358
|
+
"last_seen": null,
|
|
359
|
+
"is_deleted": false
|
|
360
|
+
},
|
|
361
|
+
"content": "Hello!",
|
|
362
|
+
"attachment": null,
|
|
363
|
+
"reply_to": null,
|
|
364
|
+
"forwarded_from": null,
|
|
365
|
+
"reactions": [],
|
|
366
|
+
"read_by": [],
|
|
367
|
+
"created_at": "2026-10-04T12:00:00Z",
|
|
368
|
+
"edited_at": null,
|
|
369
|
+
"is_deleted": false,
|
|
370
|
+
"client_temp_id": "c-1"
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
"server/presence.json": {
|
|
375
|
+
"type": "presence",
|
|
376
|
+
"event_id": "7217400317439950009",
|
|
377
|
+
"chat_id": null,
|
|
378
|
+
"data": {
|
|
379
|
+
"user_id": "1002",
|
|
380
|
+
"is_online": false,
|
|
381
|
+
"last_seen": "2026-10-04T12:00:00Z"
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
"server/reaction_add.json": {
|
|
385
|
+
"type": "reaction_add",
|
|
386
|
+
"event_id": "7217400317439950009",
|
|
387
|
+
"chat_id": "42",
|
|
388
|
+
"data": {
|
|
389
|
+
"message_id": "7217400317439950001",
|
|
390
|
+
"reaction": {
|
|
391
|
+
"emoji": "👍",
|
|
392
|
+
"user_id": "1002"
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
"server/reaction_remove.json": {
|
|
397
|
+
"type": "reaction_remove",
|
|
398
|
+
"event_id": "7217400317439950009",
|
|
399
|
+
"chat_id": "42",
|
|
400
|
+
"data": {
|
|
401
|
+
"message_id": "7217400317439950001",
|
|
402
|
+
"reaction": {
|
|
403
|
+
"emoji": "👍",
|
|
404
|
+
"user_id": "1002"
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
"server/read.json": {
|
|
409
|
+
"type": "read",
|
|
410
|
+
"event_id": "7217400317439950009",
|
|
411
|
+
"chat_id": "42",
|
|
412
|
+
"data": {
|
|
413
|
+
"message_id": "7217400317439950001",
|
|
414
|
+
"user_id": "1002",
|
|
415
|
+
"read_at": "2026-10-04T12:00:00Z"
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
"server/typing.json": {
|
|
419
|
+
"type": "typing",
|
|
420
|
+
"event_id": "7217400317439950009",
|
|
421
|
+
"chat_id": "42",
|
|
422
|
+
"data": {
|
|
423
|
+
"user_id": "1002",
|
|
424
|
+
"is_typing": true
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|