@tribe-nest/media-protocol 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.
Files changed (84) hide show
  1. package/build/_fixtures/joinTokenVectors.json +103 -0
  2. package/build/claims.d.ts +327 -0
  3. package/build/claims.d.ts.map +1 -0
  4. package/build/claims.js +91 -0
  5. package/build/claims.js.map +1 -0
  6. package/build/codecs.d.ts +102 -0
  7. package/build/codecs.d.ts.map +1 -0
  8. package/build/codecs.js +138 -0
  9. package/build/codecs.js.map +1 -0
  10. package/build/errorSchema.d.ts +8 -0
  11. package/build/errorSchema.d.ts.map +1 -0
  12. package/build/errorSchema.js +12 -0
  13. package/build/errorSchema.js.map +1 -0
  14. package/build/errors.d.ts +18 -0
  15. package/build/errors.d.ts.map +1 -0
  16. package/build/errors.js +47 -0
  17. package/build/errors.js.map +1 -0
  18. package/build/frames.d.ts +1291 -0
  19. package/build/frames.d.ts.map +1 -0
  20. package/build/frames.js +222 -0
  21. package/build/frames.js.map +1 -0
  22. package/build/grants.d.ts +307 -0
  23. package/build/grants.d.ts.map +1 -0
  24. package/build/grants.js +228 -0
  25. package/build/grants.js.map +1 -0
  26. package/build/index.d.ts +24 -0
  27. package/build/index.d.ts.map +1 -0
  28. package/build/index.js +40 -0
  29. package/build/index.js.map +1 -0
  30. package/build/mp-build/_fixtures/joinTokenVectors.json +103 -0
  31. package/build/mp-build/claims.d.ts +299 -0
  32. package/build/mp-build/claims.d.ts.map +1 -0
  33. package/build/mp-build/claims.js +60 -0
  34. package/build/mp-build/claims.js.map +1 -0
  35. package/build/mp-build/errorSchema.d.ts +8 -0
  36. package/build/mp-build/errorSchema.d.ts.map +1 -0
  37. package/build/mp-build/errorSchema.js +12 -0
  38. package/build/mp-build/errorSchema.js.map +1 -0
  39. package/build/mp-build/errors.d.ts +18 -0
  40. package/build/mp-build/errors.d.ts.map +1 -0
  41. package/build/mp-build/errors.js +47 -0
  42. package/build/mp-build/errors.js.map +1 -0
  43. package/build/mp-build/frames.d.ts +786 -0
  44. package/build/mp-build/frames.d.ts.map +1 -0
  45. package/build/mp-build/frames.js +153 -0
  46. package/build/mp-build/frames.js.map +1 -0
  47. package/build/mp-build/grants.d.ts +245 -0
  48. package/build/mp-build/grants.d.ts.map +1 -0
  49. package/build/mp-build/grants.js +135 -0
  50. package/build/mp-build/grants.js.map +1 -0
  51. package/build/mp-build/index.d.ts +23 -0
  52. package/build/mp-build/index.d.ts.map +1 -0
  53. package/build/mp-build/index.js +39 -0
  54. package/build/mp-build/index.js.map +1 -0
  55. package/build/mp-build/token.d.ts +66 -0
  56. package/build/mp-build/token.d.ts.map +1 -0
  57. package/build/mp-build/token.js +208 -0
  58. package/build/mp-build/token.js.map +1 -0
  59. package/build/mp-build/version.d.ts +9 -0
  60. package/build/mp-build/version.d.ts.map +1 -0
  61. package/build/mp-build/version.js +12 -0
  62. package/build/mp-build/version.js.map +1 -0
  63. package/build/token.d.ts +66 -0
  64. package/build/token.d.ts.map +1 -0
  65. package/build/token.js +208 -0
  66. package/build/token.js.map +1 -0
  67. package/build/version.d.ts +9 -0
  68. package/build/version.d.ts.map +1 -0
  69. package/build/version.js +12 -0
  70. package/build/version.js.map +1 -0
  71. package/package.json +53 -0
  72. package/src/_fixtures/joinTokenVectors.json +103 -0
  73. package/src/_tests/codecs.spec.ts +66 -0
  74. package/src/_tests/publishGrants.spec.ts +109 -0
  75. package/src/_tests/token.spec.ts +272 -0
  76. package/src/claims.ts +116 -0
  77. package/src/codecs.ts +165 -0
  78. package/src/errorSchema.ts +9 -0
  79. package/src/errors.ts +44 -0
  80. package/src/frames.ts +234 -0
  81. package/src/grants.ts +248 -0
  82. package/src/index.ts +23 -0
  83. package/src/token.ts +237 -0
  84. package/src/version.ts +8 -0
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * THE codec list. Every Router in the fleet is created from this array and no
4
+ * other, and every server-side participant derives what it can receive FROM it.
5
+ *
6
+ * ## Why it is pinned rather than left to mediasoup's defaults
7
+ *
8
+ * Two Routers that disagree about codecs cannot be piped. `pipeToRouter` and
9
+ * the hand-rolled equivalent in the node's `pipe.ts` both require the receiving
10
+ * Router to be able to consume the producer, and "able" is decided by this
11
+ * list. A node booted from a different build with one extra codec still pipes
12
+ * fine until the day a client happens to negotiate that codec, and then exactly
13
+ * one leg of one room goes silent, which reads as a network fault.
14
+ *
15
+ * So the list lives in one place and every Router in the fleet is created from
16
+ * it. A change here is a fleet-wide change and a deploy that mixes versions is
17
+ * a deploy that mixes codec sets, which is why `DEPLOYMENT.md` says drain
18
+ * before upgrade rather than rolling.
19
+ *
20
+ * ## Why it lives in the PROTOCOL package
21
+ *
22
+ * The node is not the only party that has to agree. A room agent, the SIP
23
+ * gateway and the RTMP ingress are participants, not nodes: each one has to
24
+ * declare RTP capabilities on `consume`, each one runs in its own app, and none
25
+ * of them may import `apps/media-server`. Every phase that needed this list
26
+ * from outside the node had exactly one alternative, which is to write a second
27
+ * copy - and a second copy is the failure the pinning exists to prevent. The
28
+ * earlier agent plan proved it: its contract test compared two hand-written
29
+ * lists to EACH OTHER and never to this one, so both could drift together and
30
+ * stay green.
31
+ *
32
+ * Codec agreement is a wire contract between parties that cannot import each
33
+ * other, which is the same reason the frames and the claims are here.
34
+ *
35
+ * ## Zero mediasoup, deliberately
36
+ *
37
+ * The types below are declared structurally rather than imported from
38
+ * mediasoup, because this package is isomorphic and a browser must be able to
39
+ * import a frame type without pulling in a C++ addon. `apps/media-server`
40
+ * re-exports these values through explicitly mediasoup-typed bindings, so if
41
+ * the two shapes ever diverge the node fails to COMPILE rather than failing to
42
+ * pipe at three in the morning.
43
+ *
44
+ * ## What is in it, and what is deliberately not
45
+ *
46
+ * Opus for audio, because it is the only audio codec every browser publishes
47
+ * and the only one worth transcoding to for SIP and for agents.
48
+ *
49
+ * VP8 and H.264 for video. VP8 is universal and simple; H.264 is what Safari
50
+ * and every hardware encoder produce, and its absence is not a quality loss but
51
+ * a "video does not work on iPhone" bug. Both are listed with the RTCP feedback
52
+ * mediasoup needs for keyframes and bandwidth estimation: `nack`, `pli` and
53
+ * `transport-cc`. Drop `pli` and a keyframe request has no wire representation,
54
+ * so every active-speaker switch shows macroblocks until the encoder happens to
55
+ * send an IDR on its own schedule.
56
+ *
57
+ * VP9 and AV1 are deliberately absent. Both are real wins and both are a
58
+ * separate decision with a measurement behind it: SVC changes what
59
+ * `setPreferredLayers` means, and adding a codec that only some clients
60
+ * negotiate widens the "one leg is silent" surface above.
61
+ */
62
+ Object.defineProperty(exports, "__esModule", { value: true });
63
+ exports.MEDIA_CODECS = void 0;
64
+ exports.audioOnlyCapabilities = audioOnlyCapabilities;
65
+ exports.MEDIA_CODECS = [
66
+ {
67
+ kind: "audio",
68
+ mimeType: "audio/opus",
69
+ clockRate: 48000,
70
+ channels: 2,
71
+ parameters: {
72
+ // In-band FEC costs a little bandwidth and buys intelligibility across
73
+ // exactly the packet loss a consultation is likely to hit.
74
+ useinbandfec: 1,
75
+ },
76
+ },
77
+ {
78
+ kind: "video",
79
+ mimeType: "video/VP8",
80
+ clockRate: 90000,
81
+ parameters: {},
82
+ rtcpFeedback: [
83
+ { type: "nack" },
84
+ { type: "nack", parameter: "pli" },
85
+ { type: "ccm", parameter: "fir" },
86
+ { type: "goog-remb" },
87
+ { type: "transport-cc" },
88
+ ],
89
+ },
90
+ {
91
+ kind: "video",
92
+ mimeType: "video/H264",
93
+ clockRate: 90000,
94
+ parameters: {
95
+ // Constrained baseline, level 3.1. `packetization-mode: 1` is what every
96
+ // browser actually offers; mode 0 exists and nothing sends it.
97
+ "packetization-mode": 1,
98
+ "profile-level-id": "42e01f",
99
+ // Without this Chrome offers H.264 and then never sends a parameter set
100
+ // out of band, so a late joiner sees nothing until the next IDR.
101
+ "level-asymmetry-allowed": 1,
102
+ },
103
+ rtcpFeedback: [
104
+ { type: "nack" },
105
+ { type: "nack", parameter: "pli" },
106
+ { type: "ccm", parameter: "fir" },
107
+ { type: "goog-remb" },
108
+ { type: "transport-cc" },
109
+ ],
110
+ },
111
+ ];
112
+ /**
113
+ * The audio half, for a participant that has no video at all.
114
+ *
115
+ * DERIVED, never written beside the list above. A hand-written copy for agents
116
+ * and SIP was compared against another hand-written copy in an earlier plan and
117
+ * never against this list, which is precisely the drift the pinning exists to
118
+ * prevent: both copies agree with each other and neither agrees with the fleet.
119
+ */
120
+ function audioOnlyCapabilities(codecs = exports.MEDIA_CODECS) {
121
+ return {
122
+ codecs: codecs
123
+ .filter((c) => c.kind === "audio")
124
+ .map((c, index) => ({
125
+ kind: "audio",
126
+ mimeType: c.mimeType,
127
+ clockRate: c.clockRate,
128
+ ...(c.channels ? { channels: c.channels } : {}),
129
+ ...(c.parameters ? { parameters: c.parameters } : {}),
130
+ ...(c.rtcpFeedback ? { rtcpFeedback: c.rtcpFeedback } : {}),
131
+ // Dynamic range. mediasoup assigns the real value per Router; this is
132
+ // only what a server-side participant claims it can receive.
133
+ preferredPayloadType: c.preferredPayloadType ?? 100 + index,
134
+ })),
135
+ headerExtensions: [],
136
+ };
137
+ }
138
+ //# sourceMappingURL=codecs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.js","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;;;AAwFH,sDAiBC;AAzEY,QAAA,YAAY,GAA2B;IAClD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,YAAY;QACtB,SAAS,EAAE,KAAK;QAChB,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE;YACV,uEAAuE;YACvE,2DAA2D;YAC3D,YAAY,EAAE,CAAC;SAChB;KACF;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,KAAK;QAChB,UAAU,EAAE,EAAE;QACd,YAAY,EAAE;YACZ,EAAE,IAAI,EAAE,MAAM,EAAE;YAChB,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE;YAClC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE;YACjC,EAAE,IAAI,EAAE,WAAW,EAAE;YACrB,EAAE,IAAI,EAAE,cAAc,EAAE;SACzB;KACF;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,YAAY;QACtB,SAAS,EAAE,KAAK;QAChB,UAAU,EAAE;YACV,yEAAyE;YACzE,+DAA+D;YAC/D,oBAAoB,EAAE,CAAC;YACvB,kBAAkB,EAAE,QAAQ;YAC5B,wEAAwE;YACxE,iEAAiE;YACjE,yBAAyB,EAAE,CAAC;SAC7B;QACD,YAAY,EAAE;YACZ,EAAE,IAAI,EAAE,MAAM,EAAE;YAChB,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE;YAClC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE;YACjC,EAAE,IAAI,EAAE,WAAW,EAAE;YACrB,EAAE,IAAI,EAAE,cAAc,EAAE;SACzB;KACF;CACF,CAAC;AAEF;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CAAC,SAAiC,oBAAY;IACjF,OAAO;QACL,MAAM,EAAE,MAAM;aACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,IAAI,EAAE,OAAgB;YACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,sEAAsE;YACtE,6DAA6D;YAC7D,oBAAoB,EAAE,CAAC,CAAC,oBAAoB,IAAI,GAAG,GAAG,KAAK;SAC5D,CAAC,CAAC;QACL,gBAAgB,EAAE,EAAE;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Separate from `errors.ts` so that file stays a plain union with no zod
4
+ * import, and so `frames.ts` can validate a code without a circular import
5
+ * through the error class.
6
+ */
7
+ export declare const mediaErrorCodeSchema: z.ZodEnum<["unauthorized", "replayed", "protocol_version", "forbidden_transport", "forbidden", "not_subscribable", "subscription_limit", "room_closed", "no_such_room", "no_such_producer", "no_such_transport", "duplicate_identity", "node_draining", "capacity", "bad_request", "internal"]>;
8
+ //# sourceMappingURL=errorSchema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorSchema.d.ts","sourceRoot":"","sources":["../src/errorSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,iSAA4B,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mediaErrorCodeSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const errors_1 = require("./errors");
6
+ /**
7
+ * Separate from `errors.ts` so that file stays a plain union with no zod
8
+ * import, and so `frames.ts` can validate a code without a circular import
9
+ * through the error class.
10
+ */
11
+ exports.mediaErrorCodeSchema = zod_1.z.enum(errors_1.MEDIA_ERROR_CODES);
12
+ //# sourceMappingURL=errorSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorSchema.js","sourceRoot":"","sources":["../src/errorSchema.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,qCAA6C;AAE7C;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,OAAC,CAAC,IAAI,CAAC,0BAAiB,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Every way the media plane refuses, named once.
3
+ *
4
+ * A closed union rather than free-text messages, because three surfaces (the
5
+ * SDK, the harness, the egress client) branch on these, and a string compare
6
+ * against prose drifts the moment someone improves the wording.
7
+ */
8
+ export declare const MEDIA_ERROR_CODES: readonly ["unauthorized", "replayed", "protocol_version", "forbidden_transport", "forbidden", "not_subscribable", "subscription_limit", "room_closed", "no_such_room", "no_such_producer", "no_such_transport", "duplicate_identity", "node_draining", "capacity", "bad_request", "internal"];
9
+ export type MediaErrorCode = (typeof MEDIA_ERROR_CODES)[number];
10
+ export declare class MediaError extends Error {
11
+ readonly code: MediaErrorCode;
12
+ /** Safe to show a user? Most of these are not. */
13
+ readonly retryable: boolean;
14
+ constructor(code: MediaErrorCode, message?: string,
15
+ /** Safe to show a user? Most of these are not. */
16
+ retryable?: boolean);
17
+ }
18
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,+RAsBpB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,qBAAa,UAAW,SAAQ,KAAK;aAEjB,IAAI,EAAE,cAAc;IAEpC,kDAAkD;aAClC,SAAS;gBAHT,IAAI,EAAE,cAAc,EACpC,OAAO,CAAC,EAAE,MAAM;IAChB,kDAAkD;IAClC,SAAS,UAAQ;CAKpC"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MediaError = exports.MEDIA_ERROR_CODES = void 0;
4
+ /**
5
+ * Every way the media plane refuses, named once.
6
+ *
7
+ * A closed union rather than free-text messages, because three surfaces (the
8
+ * SDK, the harness, the egress client) branch on these, and a string compare
9
+ * against prose drifts the moment someone improves the wording.
10
+ */
11
+ exports.MEDIA_ERROR_CODES = [
12
+ // token / session
13
+ "unauthorized", // token missing, malformed, or rejected
14
+ "replayed", // jti already consumed (a session concern, not a verifier one)
15
+ "protocol_version", // client and node disagree on MEDIA_PROTOCOL_VERSION
16
+ "forbidden_transport", // token's `transport` claim does not permit this transport kind
17
+ // authorization
18
+ "forbidden", // grants do not permit this operation
19
+ "not_subscribable", // maySubscribe said no - an information barrier, not an error
20
+ "subscription_limit", // maxSubscriptions or the active-set ceiling
21
+ // state
22
+ "room_closed",
23
+ "no_such_room",
24
+ "no_such_producer",
25
+ "no_such_transport",
26
+ "duplicate_identity", // that identity is already connected to this room
27
+ // capacity / lifecycle
28
+ "node_draining", // reconnect via the URL; the LB or a redirect will place you
29
+ "capacity",
30
+ // catch-alls
31
+ "bad_request",
32
+ "internal",
33
+ ];
34
+ class MediaError extends Error {
35
+ code;
36
+ retryable;
37
+ constructor(code, message,
38
+ /** Safe to show a user? Most of these are not. */
39
+ retryable = false) {
40
+ super(message ?? code);
41
+ this.code = code;
42
+ this.retryable = retryable;
43
+ this.name = "MediaError";
44
+ }
45
+ }
46
+ exports.MediaError = MediaError;
47
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACU,QAAA,iBAAiB,GAAG;IAC/B,kBAAkB;IAClB,cAAc,EAAW,wCAAwC;IACjE,UAAU,EAAe,+DAA+D;IACxF,kBAAkB,EAAO,qDAAqD;IAC9E,qBAAqB,EAAI,gEAAgE;IACzF,gBAAgB;IAChB,WAAW,EAAc,sCAAsC;IAC/D,kBAAkB,EAAO,8DAA8D;IACvF,oBAAoB,EAAK,6CAA6C;IACtE,QAAQ;IACR,aAAa;IACb,cAAc;IACd,kBAAkB;IAClB,mBAAmB;IACnB,oBAAoB,EAAK,kDAAkD;IAC3E,uBAAuB;IACvB,eAAe,EAAU,6DAA6D;IACtF,UAAU;IACV,aAAa;IACb,aAAa;IACb,UAAU;CACF,CAAC;AAIX,MAAa,UAAW,SAAQ,KAAK;IAEjB;IAGA;IAJlB,YACkB,IAAoB,EACpC,OAAgB;IAChB,kDAAkD;IAClC,YAAY,KAAK;QAEjC,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QALP,SAAI,GAAJ,IAAI,CAAgB;QAGpB,cAAS,GAAT,SAAS,CAAQ;QAGjC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAVD,gCAUC"}