@tribe-nest/media-client 0.1.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 (65) hide show
  1. package/README.md +68 -0
  2. package/build/core/index.d.ts +17 -0
  3. package/build/core/index.d.ts.map +1 -0
  4. package/build/core/index.js +41 -0
  5. package/build/core/index.js.map +1 -0
  6. package/build/core/reconnect.d.ts +95 -0
  7. package/build/core/reconnect.d.ts.map +1 -0
  8. package/build/core/reconnect.js +160 -0
  9. package/build/core/reconnect.js.map +1 -0
  10. package/build/core/signal.d.ts +184 -0
  11. package/build/core/signal.d.ts.map +1 -0
  12. package/build/core/signal.js +416 -0
  13. package/build/core/signal.js.map +1 -0
  14. package/build/core/socket.d.ts +57 -0
  15. package/build/core/socket.d.ts.map +1 -0
  16. package/build/core/socket.js +37 -0
  17. package/build/core/socket.js.map +1 -0
  18. package/build/core/state.d.ts +67 -0
  19. package/build/core/state.d.ts.map +1 -0
  20. package/build/core/state.js +193 -0
  21. package/build/core/state.js.map +1 -0
  22. package/build/index.d.ts +29 -0
  23. package/build/index.d.ts.map +1 -0
  24. package/build/index.js +51 -0
  25. package/build/index.js.map +1 -0
  26. package/build/protocol.d.ts +10 -0
  27. package/build/protocol.d.ts.map +1 -0
  28. package/build/protocol.js +26 -0
  29. package/build/protocol.js.map +1 -0
  30. package/build/react/index.d.ts +147 -0
  31. package/build/react/index.d.ts.map +1 -0
  32. package/build/react/index.js +319 -0
  33. package/build/react/index.js.map +1 -0
  34. package/build/room/browserDevice.d.ts +3 -0
  35. package/build/room/browserDevice.d.ts.map +1 -0
  36. package/build/room/browserDevice.js +94 -0
  37. package/build/room/browserDevice.js.map +1 -0
  38. package/build/room/device.d.ts +114 -0
  39. package/build/room/device.d.ts.map +1 -0
  40. package/build/room/device.js +3 -0
  41. package/build/room/device.js.map +1 -0
  42. package/build/room/room.d.ts +219 -0
  43. package/build/room/room.d.ts.map +1 -0
  44. package/build/room/room.js +438 -0
  45. package/build/room/room.js.map +1 -0
  46. package/package.json +69 -0
  47. package/src/_tests/clientBoundary.spec.ts +110 -0
  48. package/src/core/_tests/coreBoundary.spec.ts +70 -0
  49. package/src/core/_tests/fakeSignalServer.ts +188 -0
  50. package/src/core/_tests/reconnect.spec.ts +180 -0
  51. package/src/core/_tests/signal.spec.ts +347 -0
  52. package/src/core/_tests/state.spec.ts +226 -0
  53. package/src/core/index.ts +63 -0
  54. package/src/core/reconnect.ts +233 -0
  55. package/src/core/signal.ts +527 -0
  56. package/src/core/socket.ts +58 -0
  57. package/src/core/state.ts +251 -0
  58. package/src/index.ts +54 -0
  59. package/src/protocol.ts +9 -0
  60. package/src/react/_tests/hooks.spec.tsx +509 -0
  61. package/src/react/index.tsx +439 -0
  62. package/src/room/_tests/room.spec.ts +595 -0
  63. package/src/room/browserDevice.ts +114 -0
  64. package/src/room/device.ts +119 -0
  65. package/src/room/room.ts +600 -0
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initialRoomState = void 0;
4
+ exports.reduceRoomState = reduceRoomState;
5
+ exports.reduceRoomStateAll = reduceRoomStateAll;
6
+ exports.producerById = producerById;
7
+ exports.producersOf = producersOf;
8
+ exports.activeProducers = activeProducers;
9
+ exports.visibleProducers = visibleProducers;
10
+ const media_protocol_1 = require("@tribe-nest/media-protocol");
11
+ exports.initialRoomState = {
12
+ phase: "idle",
13
+ identity: null,
14
+ room: null,
15
+ peers: [],
16
+ producers: [],
17
+ activeSpeakers: [],
18
+ recording: false,
19
+ subscribeRule: null,
20
+ draining: null,
21
+ closedReason: null,
22
+ };
23
+ /**
24
+ * Apply one event frame.
25
+ *
26
+ * Never throws and never mutates. An event it does not recognise returns the
27
+ * SAME reference, so a caller can use identity to skip a re-render.
28
+ */
29
+ function reduceRoomState(state, frame) {
30
+ switch (frame.event) {
31
+ case "joined":
32
+ // A wholesale replace, not a merge. `joined` also arrives after a
33
+ // reconnect, and merging would leave peers who left while we were away
34
+ // on screen for the rest of the call.
35
+ return {
36
+ phase: "joined",
37
+ identity: frame.identity,
38
+ room: frame.room,
39
+ peers: frame.peers,
40
+ producers: frame.producers.map((p) => ({ ...p, paused: false })),
41
+ activeSpeakers: [],
42
+ recording: frame.recording,
43
+ subscribeRule: null,
44
+ draining: null,
45
+ closedReason: null,
46
+ };
47
+ case "peerJoined": {
48
+ const without = state.peers.filter((p) => p.identity !== frame.peer.identity);
49
+ return { ...state, peers: [...without, frame.peer] };
50
+ }
51
+ case "peerLeft": {
52
+ if (!state.peers.some((p) => p.identity === frame.identity) &&
53
+ !state.producers.some((p) => p.identity === frame.identity)) {
54
+ return state;
55
+ }
56
+ // Their producers go with them. A `producerClosed` for each is NOT
57
+ // guaranteed when a peer's socket simply dies, and a producer left behind
58
+ // is a tile that never clears.
59
+ const producers = state.producers.filter((p) => p.identity !== frame.identity);
60
+ return {
61
+ ...state,
62
+ peers: state.peers.filter((p) => p.identity !== frame.identity),
63
+ producers,
64
+ activeSpeakers: pruneSpeakers(state.activeSpeakers, producers),
65
+ };
66
+ }
67
+ case "producerAppeared": {
68
+ if (state.producers.some((p) => p.producerId === frame.producerId))
69
+ return state;
70
+ return {
71
+ ...state,
72
+ producers: [
73
+ ...state.producers,
74
+ { producerId: frame.producerId, identity: frame.identity, kind: frame.kind, paused: false },
75
+ ],
76
+ };
77
+ }
78
+ case "producerClosed": {
79
+ if (!state.producers.some((p) => p.producerId === frame.producerId))
80
+ return state;
81
+ const producers = state.producers.filter((p) => p.producerId !== frame.producerId);
82
+ return { ...state, producers, activeSpeakers: pruneSpeakers(state.activeSpeakers, producers) };
83
+ }
84
+ case "producerPaused": {
85
+ let changed = false;
86
+ const producers = state.producers.map((p) => {
87
+ if (p.producerId !== frame.producerId || p.paused === frame.paused)
88
+ return p;
89
+ changed = true;
90
+ return { ...p, paused: frame.paused };
91
+ });
92
+ return changed ? { ...state, producers } : state;
93
+ }
94
+ case "activeSpeakers":
95
+ // Stored as sent, INCLUDING ids we have not seen a `producerAppeared` for
96
+ // yet. The node decides the active set; dropping an id because our view
97
+ // is a frame behind would silently discard a stream we were told to take.
98
+ return { ...state, activeSpeakers: frame.producerIds };
99
+ case "recordingChanged":
100
+ return state.recording === frame.recording ? state : { ...state, recording: frame.recording };
101
+ case "draining":
102
+ return { ...state, draining: { reconnectAfterMs: frame.reconnectAfterMs } };
103
+ case "roomClosed":
104
+ // Producers and speakers are gone for good; peers are kept so a caller can
105
+ // still say who was in the room on the "call ended" screen.
106
+ return { ...state, phase: "closed", closedReason: frame.reason, producers: [], activeSpeakers: [] };
107
+ case "subscribeRuleChanged":
108
+ /**
109
+ * A rule that only applied at join would not apply to anyone already in
110
+ * the room, which is why the node pushes it.
111
+ *
112
+ * The rule is STORED and visibility is DERIVED, never pruned. Filtering
113
+ * `producers` here loses them permanently: the wire carries any
114
+ * `SubscribeRule`, `{ mode: "all" }` included, so a node WIDENING a
115
+ * barrier mid-call could never restore what a narrowing had deleted -
116
+ * there is no `producerAppeared` replay to bring them back.
117
+ *
118
+ * Active speakers are still pruned, because that set is the node's
119
+ * instruction about what to consume RIGHT NOW rather than a record of
120
+ * what exists.
121
+ */
122
+ return {
123
+ ...state,
124
+ subscribeRule: frame.subscribe,
125
+ activeSpeakers: pruneSpeakers(state.activeSpeakers, visibleUnder(frame.subscribe, state.identity, state.producers)),
126
+ };
127
+ default:
128
+ return state;
129
+ }
130
+ }
131
+ /** Convenience for a caller feeding a stream of frames, e.g. `signal.onAny`. */
132
+ function reduceRoomStateAll(state, frames) {
133
+ return frames.reduce(reduceRoomState, state);
134
+ }
135
+ function producerById(state, producerId) {
136
+ return state.producers.find((p) => p.producerId === producerId);
137
+ }
138
+ function producersOf(state, identity) {
139
+ return state.producers.filter((p) => p.identity === identity);
140
+ }
141
+ /** The active set intersected with what we actually know about, in the node's order. */
142
+ function activeProducers(state) {
143
+ const entries = [];
144
+ for (const id of state.activeSpeakers) {
145
+ const found = producerById(state, id);
146
+ if (found)
147
+ entries.push(found);
148
+ }
149
+ return entries;
150
+ }
151
+ /** Producers permitted under `rule`. Pure. */
152
+ function visibleUnder(rule, self, producers) {
153
+ if (self === null || rule === null)
154
+ return producers;
155
+ return producers.filter((p) => permits(rule, self, p.identity));
156
+ }
157
+ /**
158
+ * What this participant may actually receive.
159
+ *
160
+ * Derived rather than stored, so a barrier that narrows and later widens is
161
+ * reversible. The reducer keeps every producer it has been told about; this is
162
+ * what a UI should render and what a client should consume from.
163
+ */
164
+ function visibleProducers(state) {
165
+ return visibleUnder(state.subscribeRule, state.identity, state.producers);
166
+ }
167
+ function pruneSpeakers(speakers, producers) {
168
+ const live = new Set(producers.map((p) => p.producerId));
169
+ const kept = speakers.filter((id) => live.has(id));
170
+ return kept.length === speakers.length ? speakers : kept;
171
+ }
172
+ /**
173
+ * Asks the CONTRACT's decision function rather than reading the rule here.
174
+ *
175
+ * Re-implementing "deny wins, an absent rule means everyone" locally is how the
176
+ * client and the node end up disagreeing about who is visible, and the client's
177
+ * version is the one a user sees. The producer half (`visibleTo`) is enforced by
178
+ * the node from the owner's stored grants and is not knowable here, so this
179
+ * applies our half only.
180
+ */
181
+ function permits(rule, selfIdentity, producerIdentity) {
182
+ if (selfIdentity === producerIdentity)
183
+ return true;
184
+ const consumerGrants = {
185
+ canPublish: false,
186
+ canSubscribe: true,
187
+ canPublishData: false,
188
+ subscribe: rule,
189
+ };
190
+ const ownerGrants = { canPublish: true, canSubscribe: false, canPublishData: false };
191
+ return (0, media_protocol_1.maySubscribe)({ consumerIdentity: selfIdentity, consumerGrants, producerIdentity, ownerGrants });
192
+ }
193
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":";;;AAiEA,0CAkHC;AAGD,gDAEC;AAED,oCAEC;AAED,kCAEC;AAGD,0CAOC;AAmBD,4CAEC;AA/ND,+DAA4H;AA8C/G,QAAA,gBAAgB,GAAc;IACzC,KAAK,EAAE,MAAM;IACb,QAAQ,EAAE,IAAI;IACd,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,EAAE;IACT,SAAS,EAAE,EAAE;IACb,cAAc,EAAE,EAAE;IAClB,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,IAAI;IACnB,QAAQ,EAAE,IAAI;IACd,YAAY,EAAE,IAAI;CACnB,CAAC;AAEF;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,KAAgB,EAAE,KAAiB;IACjE,QAAQ,KAAK,CAAC,KAAK,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,kEAAkE;YAClE,uEAAuE;YACvE,sCAAsC;YACtC,OAAO;gBACL,KAAK,EAAE,QAAQ;gBACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAChE,cAAc,EAAE,EAAE;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,aAAa,EAAE,IAAI;gBACnB,QAAQ,EAAE,IAAI;gBACd,YAAY,EAAE,IAAI;aACnB,CAAC;QAEJ,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9E,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,IACE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC;gBACvD,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC,EAC3D,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,mEAAmE;YACnE,0EAA0E;YAC1E,+BAA+B;YAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC/E,OAAO;gBACL,GAAG,KAAK;gBACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC;gBAC/D,SAAS;gBACT,cAAc,EAAE,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,SAAS,CAAC;aAC/D,CAAC;QACJ,CAAC;QAED,KAAK,kBAAkB,CAAC,CAAC,CAAC;YACxB,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC;gBAAE,OAAO,KAAK,CAAC;YACjF,OAAO;gBACL,GAAG,KAAK;gBACR,SAAS,EAAE;oBACT,GAAG,KAAK,CAAC,SAAS;oBAClB,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;iBAC5F;aACF,CAAC;QACJ,CAAC;QAED,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC;gBAAE,OAAO,KAAK,CAAC;YAClF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC;YACnF,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,CAAC;QACjG,CAAC;QAED,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC1C,IAAI,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;oBAAE,OAAO,CAAC,CAAC;gBAC7E,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;YACxC,CAAC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;QACnD,CAAC;QAED,KAAK,gBAAgB;YACnB,0EAA0E;YAC1E,wEAAwE;YACxE,0EAA0E;YAC1E,OAAO,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;QAEzD,KAAK,kBAAkB;YACrB,OAAO,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;QAEhG,KAAK,UAAU;YACb,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAE9E,KAAK,YAAY;YACf,2EAA2E;YAC3E,4DAA4D;YAC5D,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;QAEtG,KAAK,sBAAsB;YACzB;;;;;;;;;;;;;eAaG;YACH,OAAO;gBACL,GAAG,KAAK;gBACR,aAAa,EAAE,KAAK,CAAC,SAAS;gBAC9B,cAAc,EAAE,aAAa,CAC3B,KAAK,CAAC,cAAc,EACpB,YAAY,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAC/D;aACF,CAAC;QAEJ;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,SAAgB,kBAAkB,CAAC,KAAgB,EAAE,MAA6B;IAChF,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED,SAAgB,YAAY,CAAC,KAAgB,EAAE,UAAkB;IAC/D,OAAO,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;AAClE,CAAC;AAED,SAAgB,WAAW,CAAC,KAAgB,EAAE,QAAgB;IAC5D,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;AAChE,CAAC;AAED,wFAAwF;AACxF,SAAgB,eAAe,CAAC,KAAgB;IAC9C,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtC,IAAI,KAAK;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8CAA8C;AAC9C,SAAS,YAAY,CACnB,IAA0B,EAC1B,IAAmB,EACnB,SAAmC;IAEnC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrD,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAAC,KAAgB;IAC/C,OAAO,YAAY,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,aAAa,CAAC,QAA2B,EAAE,SAAmC;IACrF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,OAAO,CAAC,IAAmB,EAAE,YAAoB,EAAE,gBAAwB;IAClF,IAAI,YAAY,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,cAAc,GAAgB;QAClC,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,IAAI;QAClB,cAAc,EAAE,KAAK;QACrB,SAAS,EAAE,IAAI;KAChB,CAAC;IACF,MAAM,WAAW,GAAgB,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IAClG,OAAO,IAAA,6BAAY,EAAC,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC,CAAC;AACzG,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { MediaRoom, type MediaRoomOptions } from "./room/room";
2
+ /**
3
+ * `@tribe-nest/media-client` - the browser SDK.
4
+ *
5
+ * This is the entry point that KNOWS about the browser, and the only one that
6
+ * pulls in `mediasoup-client`. Importing it from a Node process works right up
7
+ * until a transport is created, which is why the harness, the egress client and
8
+ * the SIP gateway import `./core` instead.
9
+ *
10
+ * Three subpaths, three audiences:
11
+ *
12
+ * `.` browsers. Room, publish, subscribe, tracks.
13
+ * `./react` the same, as hooks.
14
+ * `./core` anything that is not a browser. No DOM, no mediasoup-client.
15
+ * `./protocol` the wire types, for whoever only needs to speak it.
16
+ */
17
+ export * from "./room/room";
18
+ export { type IceServer, type MediaConsumerHandle, type MediaDevice, type MediaDeviceFactory, type MediaProducerHandle, type MediaTransport, type TransportDescription, type TransportHandlers, } from "./room/device";
19
+ export { createBrowserDevice } from "./room/browserDevice";
20
+ export { type DisconnectCause, type MediaCoreCredentials, type ProducerEntry, type RoomState, type SignalLogLevel, } from "./core";
21
+ /**
22
+ * Connect to a room in a browser.
23
+ *
24
+ * The device is defaulted HERE rather than inside `room.ts`, so that the room
25
+ * logic never imports `mediasoup-client` and stays testable without a browser.
26
+ * Pass `device` explicitly to substitute one.
27
+ */
28
+ export declare function connectToRoom(options: Omit<MediaRoomOptions, "device"> & Partial<Pick<MediaRoomOptions, "device">>): Promise<MediaRoom>;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AAEH,cAAc,aAAa,CAAC;AAC5B,OAAO,EACL,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,cAAc,GACpB,MAAM,QAAQ,CAAC;AAEhB;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,GACpF,OAAO,CAAC,SAAS,CAAC,CAIpB"}
package/build/index.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createBrowserDevice = void 0;
18
+ exports.connectToRoom = connectToRoom;
19
+ const browserDevice_1 = require("./room/browserDevice");
20
+ const room_1 = require("./room/room");
21
+ /**
22
+ * `@tribe-nest/media-client` - the browser SDK.
23
+ *
24
+ * This is the entry point that KNOWS about the browser, and the only one that
25
+ * pulls in `mediasoup-client`. Importing it from a Node process works right up
26
+ * until a transport is created, which is why the harness, the egress client and
27
+ * the SIP gateway import `./core` instead.
28
+ *
29
+ * Three subpaths, three audiences:
30
+ *
31
+ * `.` browsers. Room, publish, subscribe, tracks.
32
+ * `./react` the same, as hooks.
33
+ * `./core` anything that is not a browser. No DOM, no mediasoup-client.
34
+ * `./protocol` the wire types, for whoever only needs to speak it.
35
+ */
36
+ __exportStar(require("./room/room"), exports);
37
+ var browserDevice_2 = require("./room/browserDevice");
38
+ Object.defineProperty(exports, "createBrowserDevice", { enumerable: true, get: function () { return browserDevice_2.createBrowserDevice; } });
39
+ /**
40
+ * Connect to a room in a browser.
41
+ *
42
+ * The device is defaulted HERE rather than inside `room.ts`, so that the room
43
+ * logic never imports `mediasoup-client` and stays testable without a browser.
44
+ * Pass `device` explicitly to substitute one.
45
+ */
46
+ async function connectToRoom(options) {
47
+ const room = new room_1.MediaRoom({ ...options, device: options.device ?? browserDevice_1.createBrowserDevice });
48
+ await room.connect();
49
+ return room;
50
+ }
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA+CA,sCAMC;AArDD,wDAA2D;AAC3D,sCAA+D;AAE/D;;;;;;;;;;;;;;GAcG;AAEH,8CAA4B;AAW5B,sDAA2D;AAAlD,oHAAA,mBAAmB,OAAA;AAU5B;;;;;;GAMG;AACI,KAAK,UAAU,aAAa,CACjC,OAAqF;IAErF,MAAM,IAAI,GAAG,IAAI,gBAAS,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,mCAAmB,EAAE,CAAC,CAAC;IAC1F,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@tribe-nest/media-client/protocol` is a re-export of the frozen contract and
3
+ * nothing else.
4
+ *
5
+ * It exists so an SDK consumer needs one dependency rather than two, and so
6
+ * there is a visible place that would have to be edited if anyone ever tried to
7
+ * "extend" a frame locally. Nothing is added here. Ever.
8
+ */
9
+ export * from "@tribe-nest/media-protocol";
10
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * `@tribe-nest/media-client/protocol` is a re-export of the frozen contract and
19
+ * nothing else.
20
+ *
21
+ * It exists so an SDK consumer needs one dependency rather than two, and so
22
+ * there is a visible place that would have to be edited if anyone ever tried to
23
+ * "extend" a frame locally. Nothing is added here. Ever.
24
+ */
25
+ __exportStar(require("@tribe-nest/media-protocol"), exports);
26
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;;;;GAOG;AACH,6DAA2C"}
@@ -0,0 +1,147 @@
1
+ import { type ReactNode } from "react";
2
+ import type { MediaGrants, Peer } from "@tribe-nest/media-protocol";
3
+ import { MediaRoom, type ConnectionState, type LocalPublication, type MediaRoomOptions, type MediaTrack } from "../room/room";
4
+ import type { DisconnectCause, ProducerEntry, RoomState } from "../core";
5
+ /**
6
+ * The React surface.
7
+ *
8
+ * ## Why `useSyncExternalStore` rather than state in the provider
9
+ *
10
+ * The room is an external mutable object with its own event loop, which is
11
+ * precisely the case React 18 added that hook for. The alternative - mirroring
12
+ * room state into `useState` on every event - tears: a component reading
13
+ * `room.tracks` directly during a render sees a newer value than the mirrored
14
+ * copy it was rendered from, and the bug shows up as a tile that is one event
15
+ * behind exactly when the room is busiest.
16
+ *
17
+ * ## Why every hook takes NO arguments
18
+ *
19
+ * `useMediaRoom()` reads the provider. A hook that took credentials would let
20
+ * two components mount two rooms against one call, and the second would take
21
+ * the identity from the first and be refused with `duplicate_identity`. One
22
+ * room per provider, and the provider owns its lifetime.
23
+ */
24
+ type MediaRoomContextValue = {
25
+ room: MediaRoom | null;
26
+ connectionState: ConnectionState;
27
+ error: DisconnectCause | undefined;
28
+ /**
29
+ * Is the SDK actually coming back on its own?
30
+ *
31
+ * `connectionState` is `reconnecting` both while an attempt is booked and
32
+ * after the policy has given up, so a screen reading only that spins for ever
33
+ * over a call that is finished. This is the half that says which, and it is
34
+ * what a UI turns "wait" against "press something" on.
35
+ */
36
+ recovering: boolean;
37
+ retry: () => void;
38
+ };
39
+ export type MediaRoomProviderProps = Omit<MediaRoomOptions, "device"> & Partial<Pick<MediaRoomOptions, "device">> & {
40
+ children: ReactNode;
41
+ /** Rendered while the first connection is in flight. */
42
+ fallback?: ReactNode;
43
+ };
44
+ export declare function MediaRoomProvider({ children, fallback, device, ...options }: MediaRoomProviderProps): ReactNode;
45
+ export declare function useMediaRoom(): MediaRoomContextValue;
46
+ export declare function useRoomState(): RoomState;
47
+ export declare function useParticipants(): readonly Peer[];
48
+ export declare function useActiveSpeakers(): readonly string[];
49
+ /**
50
+ * The producers this participant may actually receive. What a UI renders.
51
+ *
52
+ * NOT `useRoomState().producers`, and the difference is the subscribe barrier.
53
+ * The reducer keeps every producer the node has ever announced, deliberately,
54
+ * so that a rule which narrows mid-call and later widens is reversible - there
55
+ * is no `producerAppeared` replay to bring back a producer it had deleted. The
56
+ * price of keeping them is that the raw list contains people this seat has been
57
+ * barred from, and on a sealed consultation room the mere PRESENCE of the other
58
+ * party is most of what the barrier was hiding. A UI mapping over the raw list
59
+ * therefore draws a tile carrying their identity and mounts an `<audio>` for
60
+ * them, and the node's own filtering does not save it: `revokeNewlyForbidden`
61
+ * only closes producers this session had a live consumer for, so one that was
62
+ * announced and never consumed stays in the list for the rest of the call.
63
+ *
64
+ * Memoised on the state OBJECT rather than used as a store selector: the filter
65
+ * builds a new array per call, and `useSyncExternalStore` compares snapshots
66
+ * with `Object.is`, so reading it through `useRoomSnapshot` would re-render for
67
+ * ever. The reducer returns the same state object when nothing changed, which
68
+ * is what makes this settle.
69
+ */
70
+ export declare function useVisibleProducers(): readonly ProducerEntry[];
71
+ /**
72
+ * What THIS participant is publishing right now, with the live tracks.
73
+ *
74
+ * `useLocalMedia()` answers "is the camera on" and that is enough for a button,
75
+ * but a local preview needs the `MediaStreamTrack` itself, and reading
76
+ * `room.localPublications` from `useMediaRoom()` is not reactive: the preview
77
+ * would stay black until something else happened to re-render it.
78
+ *
79
+ * The snapshot is the room's cached array, rebuilt only when a publication
80
+ * changed, which is what keeps this out of the `getSnapshot` loop.
81
+ */
82
+ export declare function useLocalPublications(): readonly LocalPublication[];
83
+ /** What the NODE says this token may do. For rendering, never for a decision:
84
+ * the node enforces independently and is the only side that counts. */
85
+ export declare function useGrants(): MediaGrants | undefined;
86
+ /** True while any egress participant is present. A consent signal. */
87
+ export declare function useRecording(): boolean;
88
+ /**
89
+ * One remote track, and a ref callback that attaches it.
90
+ *
91
+ * `attach` exists because `srcObject` cannot be set through JSX: it takes a
92
+ * `MediaStream` object, not a string, so every consumer of this hook would
93
+ * otherwise write the same `useEffect`.
94
+ *
95
+ * ## Why it is keyed on the TRACK
96
+ *
97
+ * A ref callback runs when the element mounts and again only when the callback
98
+ * IDENTITY changes. The track almost never exists at mount: a producer reaches
99
+ * `state.producers` the moment the node announces it, and the consume round
100
+ * trip that produces the track finishes some milliseconds later. So a callback
101
+ * memoised on `[room, producerId]` - both stable - ran exactly once, against no
102
+ * track, set `srcObject` to `null` and was never called again. An element that
103
+ * is only ever mounted once therefore stayed silent for the whole call, which
104
+ * is what happened to remote AUDIO: a `<video>` escapes it because a tile
105
+ * renders one only after the track arrives, and every remote `<video>` is
106
+ * `muted`, so a call had picture and no sound in either direction.
107
+ *
108
+ * Depending on `track` is what makes React re-run it: the identity changes when
109
+ * the track appears, React calls the old callback with `null` and the new one
110
+ * with the element. `MediaTrack` objects are created once per consumer and the
111
+ * room's snapshot array is rebuilt only when a track changes, so this settles
112
+ * rather than churning.
113
+ */
114
+ export declare function useRemoteTrack(producerId: string): {
115
+ track: MediaTrack | undefined;
116
+ attach: (element: HTMLMediaElement | null) => void;
117
+ };
118
+ export type LocalSource = "camera" | "microphone" | "screen";
119
+ export type LocalMediaControls = {
120
+ publishCamera: () => Promise<void>;
121
+ publishMicrophone: () => Promise<void>;
122
+ publishScreen: () => Promise<void>;
123
+ unpublish: (source: LocalSource) => Promise<void>;
124
+ isCameraEnabled: boolean;
125
+ isMicrophoneEnabled: boolean;
126
+ screenSharing: boolean;
127
+ /**
128
+ * A capture or a publish is in flight for this source.
129
+ *
130
+ * Exposed so a control can be disabled rather than silently ignoring the
131
+ * second press: a permission prompt takes seconds, during which every flag
132
+ * above still reads `false`, and a button that looks unchanged is a button
133
+ * people press again.
134
+ */
135
+ pending: Record<LocalSource, boolean>;
136
+ error: Error | undefined;
137
+ };
138
+ /**
139
+ * Capture and publish.
140
+ *
141
+ * `getUserMedia` lives here rather than in the room because permission is a
142
+ * BROWSER concern with a UI consequence - a denied prompt is something a person
143
+ * has to be told about - and the room has no way to say so.
144
+ */
145
+ export declare function useLocalMedia(): LocalMediaControls;
146
+ export {};
147
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EACL,SAAS,EACT,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzE;;;;;;;;;;;;;;;;;;GAkBG;AAEH,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,eAAe,EAAE,eAAe,CAAC;IACjC,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC;;;;;;;OAOG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAIF,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GACnE,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,GAAG;IAC1C,QAAQ,EAAE,SAAS,CAAC;IACpB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEJ,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,sBAAsB,GAAG,SAAS,CAsD/G;AAQD,wBAAgB,YAAY,IAAI,qBAAqB,CAEpD;AAiCD,wBAAgB,YAAY,IAAI,SAAS,CAExC;AAED,wBAAgB,eAAe,IAAI,SAAS,IAAI,EAAE,CAEjD;AAED,wBAAgB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAErD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,IAAI,SAAS,aAAa,EAAE,CAG9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,IAAI,SAAS,gBAAgB,EAAE,CAElE;AAED;uEACuE;AACvE,wBAAgB,SAAS,IAAI,WAAW,GAAG,SAAS,CAGnD;AAED,sEAAsE;AACtE,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;CACpD,CAaA;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACtC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;CAC1B,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,kBAAkB,CAkHlD"}