@myronsi/messenger-api 2.1.0-alpha.15 → 2.1.0-alpha.16.next.89
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 +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/openapi.yaml +80 -4
- package/dist/schema.d.ts +106 -4
- package/dist/ws-events.d.ts +3 -1
- package/dist/ws-events.schema.json +42 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Contract changes only. The backend changelog is `CHANGELOG.md` in the repository root. Rules: `docs/api-compatibility.md`.
|
|
4
4
|
|
|
5
|
+
## 2.1.0-alpha.16
|
|
6
|
+
|
|
7
|
+
- Trusted-device recovery no longer needs the current password. `DeviceRecoveryApproval.password` is no longer
|
|
8
|
+
required, is deprecated and ignored. New optional `passkey` (`PasskeyVerification`).
|
|
9
|
+
- `POST /auth/recovery-requests/{requestId}/approve/options` (`DeviceRecoveryApprovalOptions`: `method` `passkey`
|
|
10
|
+
with `challenge_id` and `public_key`, or `session` with `approvable_from` while the session is too new).
|
|
11
|
+
- New error codes `user_verification_required` (account has passkeys, no assertion sent) and `session_too_new`
|
|
12
|
+
(no passkeys and the session is younger than the minimum session age), both `403` on the approval.
|
|
13
|
+
|
|
5
14
|
## 2.1.0-alpha.15
|
|
6
15
|
|
|
7
16
|
- End-to-end encrypted audio calls in direct chats and groups (tag `calls`, see `docs/calls.md` and
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Generated from api/openapi.yaml 2.1.0-alpha.
|
|
2
|
-
export declare const API_VERSION: "2.1.0-alpha.
|
|
1
|
+
/* Generated from api/openapi.yaml 2.1.0-alpha.16. Do not edit. */
|
|
2
|
+
export declare const API_VERSION: "2.1.0-alpha.16";
|
|
3
3
|
export type { paths, components, operations } from "./schema.js";
|
|
4
4
|
export * from "./ws-events.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const API_VERSION = "2.1.0-alpha.
|
|
1
|
+
export const API_VERSION = "2.1.0-alpha.16";
|
package/dist/openapi.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: Messenger API
|
|
4
|
-
version: 2.1.0-alpha.
|
|
4
|
+
version: 2.1.0-alpha.16
|
|
5
5
|
summary: REST contract of the Messenger backend (v2).
|
|
6
6
|
description: |
|
|
7
7
|
Contract for the Go backend. The WebSocket protocol is described in `websocket/` and
|
|
@@ -2172,7 +2172,22 @@ paths:
|
|
|
2172
2172
|
tags:
|
|
2173
2173
|
- auth
|
|
2174
2174
|
operationId: approveDeviceRecovery
|
|
2175
|
-
summary: Approve a recovery request using number matching,
|
|
2175
|
+
summary: Approve a recovery request using number matching, user verification and the second factor
|
|
2176
|
+
description: |
|
|
2177
|
+
Approval does not need the current password, which the user may have forgotten. The checks run in this
|
|
2178
|
+
order: the request must belong to the caller and `match_number` must match (a wrong number denies the
|
|
2179
|
+
request), then user verification, then the second factor.
|
|
2180
|
+
|
|
2181
|
+
User verification follows `POST /auth/recovery-requests/{requestId}/approve/options`. When the account
|
|
2182
|
+
has passkeys, `passkey` must carry an assertion with user verification for the challenge from that call
|
|
2183
|
+
(`403 user_verification_required` without it, `403 invalid_credentials` for a failed assertion,
|
|
2184
|
+
`400 invalid_request` for an unknown, used or expired challenge). A missing `code` answers
|
|
2185
|
+
`401 two_factor_required` before the challenge is used, so the request can be repeated with the same
|
|
2186
|
+
assertion and the code. A wrong code is checked after the passkey and uses up the challenge.
|
|
2187
|
+
Without passkeys the approving session must be at least as old as the minimum session age
|
|
2188
|
+
(`RECOVERY_APPROVAL_MIN_SESSION_AGE`, 72 hours by default), otherwise `403 session_too_new`.
|
|
2189
|
+
|
|
2190
|
+
`password` is ignored and only accepted so that older clients keep working.
|
|
2176
2191
|
requestBody:
|
|
2177
2192
|
required: true
|
|
2178
2193
|
content:
|
|
@@ -2201,6 +2216,41 @@ paths:
|
|
|
2201
2216
|
$ref: "#/components/responses/ClientOutdated"
|
|
2202
2217
|
"429":
|
|
2203
2218
|
$ref: "#/components/responses/TooManyRequests"
|
|
2219
|
+
/auth/recovery-requests/{requestId}/approve/options:
|
|
2220
|
+
parameters:
|
|
2221
|
+
- *a1
|
|
2222
|
+
- *a2
|
|
2223
|
+
- *a4
|
|
2224
|
+
post:
|
|
2225
|
+
tags:
|
|
2226
|
+
- auth
|
|
2227
|
+
operationId: deviceRecoveryApprovalOptions
|
|
2228
|
+
summary: Tell the approving device how to verify its user
|
|
2229
|
+
description: |
|
|
2230
|
+
`method: passkey` when the account has passkeys: `challenge_id` and `public_key` (WebAuthn request
|
|
2231
|
+
options with `userVerification: required`) are for `navigator.credentials.get`, and the result goes to
|
|
2232
|
+
the approval as `passkey`. The challenge is bound to this session and request, is single use and expires
|
|
2233
|
+
after 5 minutes or with the request. `method: session` otherwise; `approvable_from` is set while the
|
|
2234
|
+
session is younger than the minimum session age and tells when it may approve.
|
|
2235
|
+
responses:
|
|
2236
|
+
"200":
|
|
2237
|
+
description: Success
|
|
2238
|
+
content:
|
|
2239
|
+
application/json:
|
|
2240
|
+
schema:
|
|
2241
|
+
$ref: "#/components/schemas/DeviceRecoveryApprovalOptions"
|
|
2242
|
+
"400":
|
|
2243
|
+
$ref: "#/components/responses/BadRequest"
|
|
2244
|
+
"401":
|
|
2245
|
+
$ref: "#/components/responses/Unauthorized"
|
|
2246
|
+
"403":
|
|
2247
|
+
$ref: "#/components/responses/Forbidden"
|
|
2248
|
+
"404":
|
|
2249
|
+
$ref: "#/components/responses/NotFound"
|
|
2250
|
+
"426":
|
|
2251
|
+
$ref: "#/components/responses/ClientOutdated"
|
|
2252
|
+
"429":
|
|
2253
|
+
$ref: "#/components/responses/TooManyRequests"
|
|
2204
2254
|
/auth/recovery-requests/{requestId}/deny:
|
|
2205
2255
|
parameters:
|
|
2206
2256
|
- *a1
|
|
@@ -4020,6 +4070,8 @@ components:
|
|
|
4020
4070
|
- invalid_client_version
|
|
4021
4071
|
- blocked_by_user
|
|
4022
4072
|
- approval_required
|
|
4073
|
+
- user_verification_required
|
|
4074
|
+
- session_too_new
|
|
4023
4075
|
- calls_unavailable
|
|
4024
4076
|
- calls_not_allowed
|
|
4025
4077
|
- call_identity_required
|
|
@@ -4933,18 +4985,42 @@ components:
|
|
|
4933
4985
|
type: object
|
|
4934
4986
|
required:
|
|
4935
4987
|
- match_number
|
|
4936
|
-
- password
|
|
4937
4988
|
properties:
|
|
4938
4989
|
match_number:
|
|
4939
4990
|
type: integer
|
|
4940
4991
|
minimum: 10
|
|
4941
4992
|
maximum: 99
|
|
4993
|
+
passkey:
|
|
4994
|
+
$ref: "#/components/schemas/PasskeyVerification"
|
|
4995
|
+
code:
|
|
4996
|
+
type: string
|
|
4997
|
+
maxLength: 128
|
|
4942
4998
|
password:
|
|
4943
4999
|
type: string
|
|
4944
5000
|
maxLength: 1024
|
|
4945
|
-
|
|
5001
|
+
deprecated: true
|
|
5002
|
+
description: Ignored. Older clients still send it.
|
|
5003
|
+
DeviceRecoveryApprovalOptions:
|
|
5004
|
+
type: object
|
|
5005
|
+
required:
|
|
5006
|
+
- method
|
|
5007
|
+
properties:
|
|
5008
|
+
method:
|
|
5009
|
+
type: string
|
|
5010
|
+
enum: [passkey, session]
|
|
5011
|
+
challenge_id:
|
|
4946
5012
|
type: string
|
|
5013
|
+
minLength: 32
|
|
4947
5014
|
maxLength: 128
|
|
5015
|
+
description: "Present with `method: passkey`."
|
|
5016
|
+
public_key:
|
|
5017
|
+
type: object
|
|
5018
|
+
additionalProperties: true
|
|
5019
|
+
description: "WebAuthn request options, present with `method: passkey`."
|
|
5020
|
+
approvable_from:
|
|
5021
|
+
type: string
|
|
5022
|
+
format: date-time
|
|
5023
|
+
description: "Present with `method: session` while the session is too new to approve."
|
|
4948
5024
|
DeviceRecoveryComplete:
|
|
4949
5025
|
type: object
|
|
4950
5026
|
required:
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Generated from api/openapi.yaml 2.1.0-alpha.
|
|
1
|
+
/* Generated from api/openapi.yaml 2.1.0-alpha.16. Do not edit. */
|
|
2
2
|
export interface paths {
|
|
3
3
|
"/meta": {
|
|
4
4
|
parameters: {
|
|
@@ -1687,7 +1687,23 @@ export interface paths {
|
|
|
1687
1687
|
};
|
|
1688
1688
|
get?: never;
|
|
1689
1689
|
put?: never;
|
|
1690
|
-
/**
|
|
1690
|
+
/**
|
|
1691
|
+
* Approve a recovery request using number matching, user verification and the second factor
|
|
1692
|
+
* @description Approval does not need the current password, which the user may have forgotten. The checks run in this
|
|
1693
|
+
* order: the request must belong to the caller and `match_number` must match (a wrong number denies the
|
|
1694
|
+
* request), then user verification, then the second factor.
|
|
1695
|
+
*
|
|
1696
|
+
* User verification follows `POST /auth/recovery-requests/{requestId}/approve/options`. When the account
|
|
1697
|
+
* has passkeys, `passkey` must carry an assertion with user verification for the challenge from that call
|
|
1698
|
+
* (`403 user_verification_required` without it, `403 invalid_credentials` for a failed assertion,
|
|
1699
|
+
* `400 invalid_request` for an unknown, used or expired challenge). A missing `code` answers
|
|
1700
|
+
* `401 two_factor_required` before the challenge is used, so the request can be repeated with the same
|
|
1701
|
+
* assertion and the code. A wrong code is checked after the passkey and uses up the challenge.
|
|
1702
|
+
* Without passkeys the approving session must be at least as old as the minimum session age
|
|
1703
|
+
* (`RECOVERY_APPROVAL_MIN_SESSION_AGE`, 72 hours by default), otherwise `403 session_too_new`.
|
|
1704
|
+
*
|
|
1705
|
+
* `password` is ignored and only accepted so that older clients keep working.
|
|
1706
|
+
*/
|
|
1691
1707
|
post: operations["approveDeviceRecovery"];
|
|
1692
1708
|
delete?: never;
|
|
1693
1709
|
options?: never;
|
|
@@ -1695,6 +1711,38 @@ export interface paths {
|
|
|
1695
1711
|
patch?: never;
|
|
1696
1712
|
trace?: never;
|
|
1697
1713
|
};
|
|
1714
|
+
"/auth/recovery-requests/{requestId}/approve/options": {
|
|
1715
|
+
parameters: {
|
|
1716
|
+
query?: never;
|
|
1717
|
+
header?: {
|
|
1718
|
+
/** @description Version of the client app, for logs and the per-client metric. */
|
|
1719
|
+
"X-Client-Version"?: components["parameters"]["ClientVersion"];
|
|
1720
|
+
/** @description Contract version the client was built with (`API_VERSION` of `@myronsi/messenger-api`). A different MAJOR or a version below `min_client_api_version` is answered with `426`; a malformed value with `400` (`invalid_client_version`). */
|
|
1721
|
+
"X-Client-Api-Version"?: components["parameters"]["ClientApiVersion"];
|
|
1722
|
+
};
|
|
1723
|
+
path: {
|
|
1724
|
+
/** @description Approval handle from the recovery_request event. It is different from the requester's capability and cannot poll or complete the request. */
|
|
1725
|
+
requestId: string;
|
|
1726
|
+
};
|
|
1727
|
+
cookie?: never;
|
|
1728
|
+
};
|
|
1729
|
+
get?: never;
|
|
1730
|
+
put?: never;
|
|
1731
|
+
/**
|
|
1732
|
+
* Tell the approving device how to verify its user
|
|
1733
|
+
* @description `method: passkey` when the account has passkeys: `challenge_id` and `public_key` (WebAuthn request
|
|
1734
|
+
* options with `userVerification: required`) are for `navigator.credentials.get`, and the result goes to
|
|
1735
|
+
* the approval as `passkey`. The challenge is bound to this session and request, is single use and expires
|
|
1736
|
+
* after 5 minutes or with the request. `method: session` otherwise; `approvable_from` is set while the
|
|
1737
|
+
* session is younger than the minimum session age and tells when it may approve.
|
|
1738
|
+
*/
|
|
1739
|
+
post: operations["deviceRecoveryApprovalOptions"];
|
|
1740
|
+
delete?: never;
|
|
1741
|
+
options?: never;
|
|
1742
|
+
head?: never;
|
|
1743
|
+
patch?: never;
|
|
1744
|
+
trace?: never;
|
|
1745
|
+
};
|
|
1698
1746
|
"/auth/recovery-requests/{requestId}/deny": {
|
|
1699
1747
|
parameters: {
|
|
1700
1748
|
query?: never;
|
|
@@ -2549,7 +2597,7 @@ export interface components {
|
|
|
2549
2597
|
* codes must be treated like `internal_error`.
|
|
2550
2598
|
* @enum {string}
|
|
2551
2599
|
*/
|
|
2552
|
-
ErrorCode: "invalid_request" | "validation_failed" | "unauthenticated" | "invalid_credentials" | "two_factor_required" | "invalid_two_factor_code" | "token_expired" | "forbidden" | "not_found" | "conflict" | "already_exists" | "payload_too_large" | "unsupported_media_type" | "rate_limited" | "client_outdated" | "invalid_client_version" | "blocked_by_user" | "approval_required" | "calls_unavailable" | "calls_not_allowed" | "call_identity_required" | "call_full" | "call_ended" | "already_in_call" | "stale_epoch" | "internal_error";
|
|
2600
|
+
ErrorCode: "invalid_request" | "validation_failed" | "unauthenticated" | "invalid_credentials" | "two_factor_required" | "invalid_two_factor_code" | "token_expired" | "forbidden" | "not_found" | "conflict" | "already_exists" | "payload_too_large" | "unsupported_media_type" | "rate_limited" | "client_outdated" | "invalid_client_version" | "blocked_by_user" | "approval_required" | "user_verification_required" | "session_too_new" | "calls_unavailable" | "calls_not_allowed" | "call_identity_required" | "call_full" | "call_ended" | "already_in_call" | "stale_epoch" | "internal_error";
|
|
2553
2601
|
/** @description RFC 9457 problem details. `426 Upgrade Required` (`client_outdated`) uses the same format. */
|
|
2554
2602
|
Problem: {
|
|
2555
2603
|
/**
|
|
@@ -3064,8 +3112,28 @@ export interface components {
|
|
|
3064
3112
|
};
|
|
3065
3113
|
DeviceRecoveryApproval: {
|
|
3066
3114
|
match_number: number;
|
|
3067
|
-
|
|
3115
|
+
passkey?: components["schemas"]["PasskeyVerification"];
|
|
3068
3116
|
code?: string;
|
|
3117
|
+
/**
|
|
3118
|
+
* @deprecated
|
|
3119
|
+
* @description Ignored. Older clients still send it.
|
|
3120
|
+
*/
|
|
3121
|
+
password?: string;
|
|
3122
|
+
};
|
|
3123
|
+
DeviceRecoveryApprovalOptions: {
|
|
3124
|
+
/** @enum {string} */
|
|
3125
|
+
method: "passkey" | "session";
|
|
3126
|
+
/** @description Present with `method: passkey`. */
|
|
3127
|
+
challenge_id?: string;
|
|
3128
|
+
/** @description WebAuthn request options, present with `method: passkey`. */
|
|
3129
|
+
public_key?: {
|
|
3130
|
+
[key: string]: unknown;
|
|
3131
|
+
};
|
|
3132
|
+
/**
|
|
3133
|
+
* Format: date-time
|
|
3134
|
+
* @description Present with `method: session` while the session is too new to approve.
|
|
3135
|
+
*/
|
|
3136
|
+
approvable_from?: string;
|
|
3069
3137
|
};
|
|
3070
3138
|
DeviceRecoveryComplete: {
|
|
3071
3139
|
new_password: string;
|
|
@@ -6365,6 +6433,40 @@ export interface operations {
|
|
|
6365
6433
|
429: components["responses"]["TooManyRequests"];
|
|
6366
6434
|
};
|
|
6367
6435
|
};
|
|
6436
|
+
deviceRecoveryApprovalOptions: {
|
|
6437
|
+
parameters: {
|
|
6438
|
+
query?: never;
|
|
6439
|
+
header?: {
|
|
6440
|
+
/** @description Version of the client app, for logs and the per-client metric. */
|
|
6441
|
+
"X-Client-Version"?: components["parameters"]["ClientVersion"];
|
|
6442
|
+
/** @description Contract version the client was built with (`API_VERSION` of `@myronsi/messenger-api`). A different MAJOR or a version below `min_client_api_version` is answered with `426`; a malformed value with `400` (`invalid_client_version`). */
|
|
6443
|
+
"X-Client-Api-Version"?: components["parameters"]["ClientApiVersion"];
|
|
6444
|
+
};
|
|
6445
|
+
path: {
|
|
6446
|
+
/** @description Approval handle from the recovery_request event. It is different from the requester's capability and cannot poll or complete the request. */
|
|
6447
|
+
requestId: string;
|
|
6448
|
+
};
|
|
6449
|
+
cookie?: never;
|
|
6450
|
+
};
|
|
6451
|
+
requestBody?: never;
|
|
6452
|
+
responses: {
|
|
6453
|
+
/** @description Success */
|
|
6454
|
+
200: {
|
|
6455
|
+
headers: {
|
|
6456
|
+
[name: string]: unknown;
|
|
6457
|
+
};
|
|
6458
|
+
content: {
|
|
6459
|
+
"application/json": components["schemas"]["DeviceRecoveryApprovalOptions"];
|
|
6460
|
+
};
|
|
6461
|
+
};
|
|
6462
|
+
400: components["responses"]["BadRequest"];
|
|
6463
|
+
401: components["responses"]["Unauthorized"];
|
|
6464
|
+
403: components["responses"]["Forbidden"];
|
|
6465
|
+
404: components["responses"]["NotFound"];
|
|
6466
|
+
426: components["responses"]["ClientOutdated"];
|
|
6467
|
+
429: components["responses"]["TooManyRequests"];
|
|
6468
|
+
};
|
|
6469
|
+
};
|
|
6368
6470
|
denyDeviceRecovery: {
|
|
6369
6471
|
parameters: {
|
|
6370
6472
|
query?: never;
|
package/dist/ws-events.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Generated from api/openapi.yaml 2.1.0-alpha.
|
|
1
|
+
/* Generated from api/openapi.yaml 2.1.0-alpha.16. Do not edit. */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Any WebSocket event, in either direction.
|
|
@@ -111,6 +111,8 @@ export type ErrorCode =
|
|
|
111
111
|
| "invalid_client_version"
|
|
112
112
|
| "blocked_by_user"
|
|
113
113
|
| "approval_required"
|
|
114
|
+
| "user_verification_required"
|
|
115
|
+
| "session_too_new"
|
|
114
116
|
| "calls_unavailable"
|
|
115
117
|
| "calls_not_allowed"
|
|
116
118
|
| "call_identity_required"
|
|
@@ -202,6 +202,8 @@
|
|
|
202
202
|
"invalid_client_version",
|
|
203
203
|
"blocked_by_user",
|
|
204
204
|
"approval_required",
|
|
205
|
+
"user_verification_required",
|
|
206
|
+
"session_too_new",
|
|
205
207
|
"calls_unavailable",
|
|
206
208
|
"calls_not_allowed",
|
|
207
209
|
"call_identity_required",
|
|
@@ -2299,8 +2301,7 @@
|
|
|
2299
2301
|
"DeviceRecoveryApproval": {
|
|
2300
2302
|
"type": "object",
|
|
2301
2303
|
"required": [
|
|
2302
|
-
"match_number"
|
|
2303
|
-
"password"
|
|
2304
|
+
"match_number"
|
|
2304
2305
|
],
|
|
2305
2306
|
"properties": {
|
|
2306
2307
|
"match_number": {
|
|
@@ -2308,13 +2309,49 @@
|
|
|
2308
2309
|
"minimum": 10,
|
|
2309
2310
|
"maximum": 99
|
|
2310
2311
|
},
|
|
2311
|
-
"
|
|
2312
|
-
"
|
|
2313
|
-
"maxLength": 1024
|
|
2312
|
+
"passkey": {
|
|
2313
|
+
"$ref": "#/$defs/PasskeyVerification"
|
|
2314
2314
|
},
|
|
2315
2315
|
"code": {
|
|
2316
2316
|
"type": "string",
|
|
2317
2317
|
"maxLength": 128
|
|
2318
|
+
},
|
|
2319
|
+
"password": {
|
|
2320
|
+
"type": "string",
|
|
2321
|
+
"maxLength": 1024,
|
|
2322
|
+
"deprecated": true,
|
|
2323
|
+
"description": "Ignored. Older clients still send it."
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
},
|
|
2327
|
+
"DeviceRecoveryApprovalOptions": {
|
|
2328
|
+
"type": "object",
|
|
2329
|
+
"required": [
|
|
2330
|
+
"method"
|
|
2331
|
+
],
|
|
2332
|
+
"properties": {
|
|
2333
|
+
"method": {
|
|
2334
|
+
"type": "string",
|
|
2335
|
+
"enum": [
|
|
2336
|
+
"passkey",
|
|
2337
|
+
"session"
|
|
2338
|
+
]
|
|
2339
|
+
},
|
|
2340
|
+
"challenge_id": {
|
|
2341
|
+
"type": "string",
|
|
2342
|
+
"minLength": 32,
|
|
2343
|
+
"maxLength": 128,
|
|
2344
|
+
"description": "Present with `method: passkey`."
|
|
2345
|
+
},
|
|
2346
|
+
"public_key": {
|
|
2347
|
+
"type": "object",
|
|
2348
|
+
"additionalProperties": true,
|
|
2349
|
+
"description": "WebAuthn request options, present with `method: passkey`."
|
|
2350
|
+
},
|
|
2351
|
+
"approvable_from": {
|
|
2352
|
+
"type": "string",
|
|
2353
|
+
"format": "date-time",
|
|
2354
|
+
"description": "Present with `method: session` while the session is too new to approve."
|
|
2318
2355
|
}
|
|
2319
2356
|
}
|
|
2320
2357
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myronsi/messenger-api",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
3
|
+
"version": "2.1.0-alpha.16.next.89",
|
|
4
4
|
"description": "API contract of the Messenger backend: OpenAPI document, WebSocket event schemas and generated TypeScript types",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|