vg-x07df 1.10.6 → 1.11.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 +37 -0
- package/dist/channel/index.cjs +285 -283
- package/dist/channel/index.cjs.map +1 -1
- package/dist/channel/index.mjs +285 -283
- package/dist/channel/index.mjs.map +1 -1
- package/dist/index.cjs +579 -689
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +579 -689
- package/dist/index.mjs.map +1 -1
- package/dist/utils/index.cjs +962 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +43 -1
- package/dist/utils/index.d.ts +43 -1
- package/dist/utils/index.mjs +952 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/channel/index.mjs
CHANGED
|
@@ -108,28 +108,6 @@ function createLogger(namespace, options) {
|
|
|
108
108
|
}
|
|
109
109
|
return new CallpadLoggerImpl(`callpad:${namespace}`, options);
|
|
110
110
|
}
|
|
111
|
-
var DataChannelContext = createContext(
|
|
112
|
-
null
|
|
113
|
-
);
|
|
114
|
-
function useDataChannelContext() {
|
|
115
|
-
const context = useContext(DataChannelContext);
|
|
116
|
-
if (!context) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
"useDataChannelContext must be used within DataChannelProvider"
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
return context;
|
|
122
|
-
}
|
|
123
|
-
function useFeatureService(featureName) {
|
|
124
|
-
const { services } = useDataChannelContext();
|
|
125
|
-
const service = services.get(featureName);
|
|
126
|
-
if (!service) {
|
|
127
|
-
throw new Error(
|
|
128
|
-
`Feature service "${featureName}" not found. Make sure it's enabled in DataChannelProvider.`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
return service;
|
|
132
|
-
}
|
|
133
111
|
|
|
134
112
|
// src/state/types.ts
|
|
135
113
|
var defaultState = {
|
|
@@ -818,6 +796,30 @@ var ChatService = class {
|
|
|
818
796
|
return sender;
|
|
819
797
|
}
|
|
820
798
|
};
|
|
799
|
+
var DataChannelContext = createContext(
|
|
800
|
+
null
|
|
801
|
+
);
|
|
802
|
+
function useDataChannelContext() {
|
|
803
|
+
const context = useContext(DataChannelContext);
|
|
804
|
+
if (!context) {
|
|
805
|
+
throw new Error(
|
|
806
|
+
"useDataChannelContext must be used within DataChannelProvider"
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return context;
|
|
810
|
+
}
|
|
811
|
+
function useFeatureService(featureName) {
|
|
812
|
+
const { services } = useDataChannelContext();
|
|
813
|
+
const service = services.get(featureName);
|
|
814
|
+
if (!service) {
|
|
815
|
+
throw new Error(
|
|
816
|
+
`Feature service "${featureName}" not found. Make sure it's enabled in DataChannelProvider.`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
return service;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/channel/chat/useChat.ts
|
|
821
823
|
function useChat() {
|
|
822
824
|
const service = useFeatureService("chat");
|
|
823
825
|
const byId = useChatStore((state) => state.byId);
|
|
@@ -1169,13 +1171,255 @@ function useRaiseHand() {
|
|
|
1169
1171
|
};
|
|
1170
1172
|
}
|
|
1171
1173
|
var defaultState3 = {
|
|
1174
|
+
spotlightedUser: null,
|
|
1175
|
+
isSpotlighted: false
|
|
1176
|
+
};
|
|
1177
|
+
var useSpotlightStore = create()(
|
|
1178
|
+
immer((set, get) => ({
|
|
1179
|
+
...defaultState3,
|
|
1180
|
+
spotlight: (participantId, info) => set((state) => {
|
|
1181
|
+
state.spotlightedUser = {
|
|
1182
|
+
participantId,
|
|
1183
|
+
ts: Date.now(),
|
|
1184
|
+
...info && { info }
|
|
1185
|
+
};
|
|
1186
|
+
state.isSpotlighted = true;
|
|
1187
|
+
}),
|
|
1188
|
+
unspotlight: () => set((state) => {
|
|
1189
|
+
state.spotlightedUser = null;
|
|
1190
|
+
state.isSpotlighted = false;
|
|
1191
|
+
}),
|
|
1192
|
+
getSpotlightedUser: () => get().spotlightedUser,
|
|
1193
|
+
clear: () => set(() => ({
|
|
1194
|
+
spotlightedUser: null,
|
|
1195
|
+
isSpotlighted: false
|
|
1196
|
+
}))
|
|
1197
|
+
}))
|
|
1198
|
+
);
|
|
1199
|
+
function applyIncomingSpotlight(envelope) {
|
|
1200
|
+
const { spotlight, unspotlight } = useSpotlightStore.getState();
|
|
1201
|
+
switch (envelope.payload.action) {
|
|
1202
|
+
case "spotlight": {
|
|
1203
|
+
spotlight(envelope.payload.targetId, envelope.payload.info);
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
case "unspotlight": {
|
|
1207
|
+
unspotlight();
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/channel/spotlight/service.ts
|
|
1214
|
+
var logger4 = createLogger("spotlight");
|
|
1215
|
+
var SpotlightService = class {
|
|
1216
|
+
constructor(room) {
|
|
1217
|
+
this.isSubscribed = false;
|
|
1218
|
+
this.room = room;
|
|
1219
|
+
}
|
|
1220
|
+
isRoomReady() {
|
|
1221
|
+
if (!this.room) {
|
|
1222
|
+
logger4.warn("Room not initialized");
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
if (this.room.state !== ConnectionState.Connected) {
|
|
1226
|
+
logger4.warn("Room not connected", { state: this.room.state });
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
if (!this.room.localParticipant) {
|
|
1230
|
+
logger4.warn("Local participant not available");
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
return true;
|
|
1234
|
+
}
|
|
1235
|
+
subscribe() {
|
|
1236
|
+
if (this.isSubscribed) return;
|
|
1237
|
+
this.room.registerTextStreamHandler("spotlight:v1", async (reader) => {
|
|
1238
|
+
try {
|
|
1239
|
+
const text = await reader.readAll();
|
|
1240
|
+
this.handleIncoming(text);
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
logger4.error("Error reading spotlight stream", err);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
this.isSubscribed = true;
|
|
1246
|
+
logger4.info("SpotlightService subscribed");
|
|
1247
|
+
}
|
|
1248
|
+
unsubscribe() {
|
|
1249
|
+
this.isSubscribed = false;
|
|
1250
|
+
logger4.info("SpotlightService unsubscribed");
|
|
1251
|
+
}
|
|
1252
|
+
getLocalParticipantId() {
|
|
1253
|
+
return this.room.localParticipant.identity;
|
|
1254
|
+
}
|
|
1255
|
+
async spotlight(targetId, info) {
|
|
1256
|
+
if (!this.isRoomReady()) {
|
|
1257
|
+
useRtcStore.getState().addError({
|
|
1258
|
+
code: "SPOTLIGHT_ROOM_NOT_READY",
|
|
1259
|
+
message: "Cannot spotlight: room not connected",
|
|
1260
|
+
timestamp: Date.now()
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const senderInfo = this.getSenderInfo();
|
|
1265
|
+
const previousSpotlighted = useSpotlightStore.getState().getSpotlightedUser();
|
|
1266
|
+
useSpotlightStore.getState().spotlight(targetId, info);
|
|
1267
|
+
try {
|
|
1268
|
+
const envelope = {
|
|
1269
|
+
v: 1,
|
|
1270
|
+
kind: "spotlight",
|
|
1271
|
+
roomId: this.room.name,
|
|
1272
|
+
ts: Date.now(),
|
|
1273
|
+
sender: senderInfo,
|
|
1274
|
+
payload: {
|
|
1275
|
+
action: "spotlight",
|
|
1276
|
+
targetId,
|
|
1277
|
+
info
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
await this.room.localParticipant.sendText(JSON.stringify(envelope), {
|
|
1281
|
+
topic: "spotlight:v1"
|
|
1282
|
+
});
|
|
1283
|
+
logger4.info("User spotlighted", { targetId });
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
logger4.error("Failed to spotlight user", error);
|
|
1286
|
+
if (previousSpotlighted) {
|
|
1287
|
+
useSpotlightStore.getState().spotlight(
|
|
1288
|
+
previousSpotlighted.participantId,
|
|
1289
|
+
previousSpotlighted.info
|
|
1290
|
+
);
|
|
1291
|
+
} else {
|
|
1292
|
+
useSpotlightStore.getState().unspotlight();
|
|
1293
|
+
}
|
|
1294
|
+
useRtcStore.getState().addError({
|
|
1295
|
+
code: "SPOTLIGHT_SEND_FAILED",
|
|
1296
|
+
message: error instanceof Error ? error.message : "Failed to spotlight user",
|
|
1297
|
+
timestamp: Date.now()
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
async unspotlight() {
|
|
1302
|
+
if (!this.isRoomReady()) {
|
|
1303
|
+
useRtcStore.getState().addError({
|
|
1304
|
+
code: "UNSPOTLIGHT_ROOM_NOT_READY",
|
|
1305
|
+
message: "Cannot unspotlight: room not connected",
|
|
1306
|
+
timestamp: Date.now()
|
|
1307
|
+
});
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
const senderInfo = this.getSenderInfo();
|
|
1311
|
+
const currentSpotlighted = useSpotlightStore.getState().getSpotlightedUser();
|
|
1312
|
+
if (!currentSpotlighted) {
|
|
1313
|
+
logger4.debug("No user is currently spotlighted");
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const previousSpotlighted = { ...currentSpotlighted };
|
|
1317
|
+
useSpotlightStore.getState().unspotlight();
|
|
1318
|
+
try {
|
|
1319
|
+
const envelope = {
|
|
1320
|
+
v: 1,
|
|
1321
|
+
kind: "spotlight",
|
|
1322
|
+
roomId: this.room.name,
|
|
1323
|
+
ts: Date.now(),
|
|
1324
|
+
sender: senderInfo,
|
|
1325
|
+
payload: {
|
|
1326
|
+
action: "unspotlight",
|
|
1327
|
+
targetId: previousSpotlighted.participantId
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
await this.room.localParticipant.sendText(JSON.stringify(envelope), {
|
|
1331
|
+
topic: "spotlight:v1"
|
|
1332
|
+
});
|
|
1333
|
+
logger4.info("User unspotlighted");
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
logger4.error("Failed to unspotlight user", error);
|
|
1336
|
+
useSpotlightStore.getState().spotlight(previousSpotlighted.participantId, previousSpotlighted.info);
|
|
1337
|
+
useRtcStore.getState().addError({
|
|
1338
|
+
code: "UNSPOTLIGHT_SEND_FAILED",
|
|
1339
|
+
message: error instanceof Error ? error.message : "Failed to unspotlight user",
|
|
1340
|
+
timestamp: Date.now()
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
handleIncoming(text) {
|
|
1345
|
+
try {
|
|
1346
|
+
const parsed = JSON.parse(text);
|
|
1347
|
+
if (!this.isValidEnvelope(parsed)) {
|
|
1348
|
+
logger4.warn("Invalid spotlight envelope received", parsed);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (parsed.sender.id === this.getLocalParticipantId()) {
|
|
1352
|
+
logger4.debug("Ignoring self-echo spotlight message");
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
applyIncomingSpotlight(parsed);
|
|
1356
|
+
logger4.debug("Spotlight message received", {
|
|
1357
|
+
action: parsed.payload.action,
|
|
1358
|
+
targetId: parsed.payload.targetId
|
|
1359
|
+
});
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
logger4.error("Error parsing incoming spotlight message", error);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
isValidEnvelope(e) {
|
|
1365
|
+
return e && e.v === 1 && e.kind === "spotlight" && typeof e.roomId === "string" && e.roomId === this.room.name && typeof e.ts === "number" && e.ts > 0 && typeof e.sender?.id === "string" && typeof e.payload?.action === "string" && ["spotlight", "unspotlight"].includes(e.payload.action) && typeof e.payload?.targetId === "string";
|
|
1366
|
+
}
|
|
1367
|
+
getSenderInfo() {
|
|
1368
|
+
const localParticipant = this.room.localParticipant;
|
|
1369
|
+
const sender = {
|
|
1370
|
+
id: localParticipant.identity
|
|
1371
|
+
};
|
|
1372
|
+
if (localParticipant.metadata) {
|
|
1373
|
+
try {
|
|
1374
|
+
sender.info = JSON.parse(
|
|
1375
|
+
localParticipant.metadata
|
|
1376
|
+
);
|
|
1377
|
+
} catch {
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return sender;
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
var logger5 = createLogger("spotlight:hook");
|
|
1384
|
+
function useSpotlight() {
|
|
1385
|
+
const service = useFeatureService("spotlight");
|
|
1386
|
+
const getSpotlightedUser = useSpotlightStore(
|
|
1387
|
+
(state) => state.getSpotlightedUser
|
|
1388
|
+
);
|
|
1389
|
+
const isSpotlighted = useSpotlightStore((state) => state.isSpotlighted);
|
|
1390
|
+
const spotlight = useCallback(
|
|
1391
|
+
async (targetId, info) => {
|
|
1392
|
+
if (!service) {
|
|
1393
|
+
logger5.error("Cannot spotlight: service not ready");
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
return service.spotlight(targetId, info);
|
|
1397
|
+
},
|
|
1398
|
+
[service]
|
|
1399
|
+
);
|
|
1400
|
+
const unspotlight = useCallback(async () => {
|
|
1401
|
+
if (!service) {
|
|
1402
|
+
logger5.error("Cannot unspotlight: service not ready");
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
return service.unspotlight();
|
|
1406
|
+
}, [service]);
|
|
1407
|
+
return {
|
|
1408
|
+
spotlight,
|
|
1409
|
+
unspotlight,
|
|
1410
|
+
getSpotlightedUser,
|
|
1411
|
+
isSpotlighted,
|
|
1412
|
+
isReady: !!service
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
var defaultState4 = {
|
|
1172
1416
|
reactions: /* @__PURE__ */ new Map(),
|
|
1173
1417
|
participantCache: {},
|
|
1174
1418
|
ttlMs: 4e3
|
|
1175
1419
|
};
|
|
1176
1420
|
var useReactionsStore = create()(
|
|
1177
1421
|
immer((set) => ({
|
|
1178
|
-
...
|
|
1422
|
+
...defaultState4,
|
|
1179
1423
|
setReaction: (participantId, reaction) => set((state) => {
|
|
1180
1424
|
state.reactions.set(participantId, reaction);
|
|
1181
1425
|
}),
|
|
@@ -1196,7 +1440,7 @@ var useReactionsStore = create()(
|
|
|
1196
1440
|
clear: () => set(() => ({
|
|
1197
1441
|
reactions: /* @__PURE__ */ new Map(),
|
|
1198
1442
|
participantCache: {},
|
|
1199
|
-
ttlMs:
|
|
1443
|
+
ttlMs: defaultState4.ttlMs
|
|
1200
1444
|
}))
|
|
1201
1445
|
}))
|
|
1202
1446
|
);
|
|
@@ -1228,7 +1472,7 @@ function generateNonce() {
|
|
|
1228
1472
|
}
|
|
1229
1473
|
|
|
1230
1474
|
// src/channel/reactions/service.ts
|
|
1231
|
-
var
|
|
1475
|
+
var logger7 = createLogger("reactions");
|
|
1232
1476
|
var ReactionsService = class {
|
|
1233
1477
|
constructor(room) {
|
|
1234
1478
|
this.isSubscribed = false;
|
|
@@ -1239,15 +1483,15 @@ var ReactionsService = class {
|
|
|
1239
1483
|
}
|
|
1240
1484
|
isRoomReady() {
|
|
1241
1485
|
if (!this.room) {
|
|
1242
|
-
|
|
1486
|
+
logger7.warn("Room not initialized");
|
|
1243
1487
|
return false;
|
|
1244
1488
|
}
|
|
1245
1489
|
if (this.room.state !== ConnectionState.Connected) {
|
|
1246
|
-
|
|
1490
|
+
logger7.warn("Room not connected", { state: this.room.state });
|
|
1247
1491
|
return false;
|
|
1248
1492
|
}
|
|
1249
1493
|
if (!this.room.localParticipant) {
|
|
1250
|
-
|
|
1494
|
+
logger7.warn("Local participant not available");
|
|
1251
1495
|
return false;
|
|
1252
1496
|
}
|
|
1253
1497
|
return true;
|
|
@@ -1265,14 +1509,14 @@ var ReactionsService = class {
|
|
|
1265
1509
|
const text = await reader.readAll();
|
|
1266
1510
|
this.handleIncoming(text);
|
|
1267
1511
|
} catch (err) {
|
|
1268
|
-
|
|
1512
|
+
logger7.error("Error reading reactions stream", err);
|
|
1269
1513
|
}
|
|
1270
1514
|
});
|
|
1271
1515
|
this.pruneInterval = setInterval(() => {
|
|
1272
1516
|
useReactionsStore.getState().pruneExpired();
|
|
1273
1517
|
}, 1e3);
|
|
1274
1518
|
this.isSubscribed = true;
|
|
1275
|
-
|
|
1519
|
+
logger7.info("ReactionsService subscribed");
|
|
1276
1520
|
}
|
|
1277
1521
|
unsubscribe() {
|
|
1278
1522
|
this.isSubscribed = false;
|
|
@@ -1280,7 +1524,7 @@ var ReactionsService = class {
|
|
|
1280
1524
|
clearInterval(this.pruneInterval);
|
|
1281
1525
|
this.pruneInterval = void 0;
|
|
1282
1526
|
}
|
|
1283
|
-
|
|
1527
|
+
logger7.info("ReactionsService unsubscribed");
|
|
1284
1528
|
}
|
|
1285
1529
|
getLocalParticipantId() {
|
|
1286
1530
|
return this.room.localParticipant.identity;
|
|
@@ -1304,7 +1548,7 @@ var ReactionsService = class {
|
|
|
1304
1548
|
return;
|
|
1305
1549
|
}
|
|
1306
1550
|
if (!this.canSendNow()) {
|
|
1307
|
-
|
|
1551
|
+
logger7.debug("Rate limited, skipping send");
|
|
1308
1552
|
return;
|
|
1309
1553
|
}
|
|
1310
1554
|
const senderInfo = this.getSenderInfo();
|
|
@@ -1330,9 +1574,9 @@ var ReactionsService = class {
|
|
|
1330
1574
|
await this.room.localParticipant.sendText(JSON.stringify(envelope), {
|
|
1331
1575
|
topic: "reactions:v1"
|
|
1332
1576
|
});
|
|
1333
|
-
|
|
1577
|
+
logger7.debug("Reaction sent", { emoji });
|
|
1334
1578
|
} catch (error) {
|
|
1335
|
-
|
|
1579
|
+
logger7.error("Failed to send reaction", error);
|
|
1336
1580
|
useRtcStore.getState().addError({
|
|
1337
1581
|
code: "REACTIONS_SEND_FAILED",
|
|
1338
1582
|
message: error instanceof Error ? error.message : "Failed to send reaction",
|
|
@@ -1345,16 +1589,16 @@ var ReactionsService = class {
|
|
|
1345
1589
|
try {
|
|
1346
1590
|
const parsed = JSON.parse(text);
|
|
1347
1591
|
if (!this.isValidEnvelope(parsed)) {
|
|
1348
|
-
|
|
1592
|
+
logger7.warn("Invalid reaction envelope received", parsed);
|
|
1349
1593
|
return;
|
|
1350
1594
|
}
|
|
1351
1595
|
if (parsed.sender.id === this.getLocalParticipantId()) {
|
|
1352
|
-
|
|
1596
|
+
logger7.debug("Ignoring self-echo reaction");
|
|
1353
1597
|
return;
|
|
1354
1598
|
}
|
|
1355
1599
|
const lastTs = this.lastRemoteTs.get(parsed.sender.id) ?? Number.NEGATIVE_INFINITY;
|
|
1356
1600
|
if (parsed.ts < lastTs) {
|
|
1357
|
-
|
|
1601
|
+
logger7.debug("Ignoring out-of-order reaction", {
|
|
1358
1602
|
sender: parsed.sender.id,
|
|
1359
1603
|
ts: parsed.ts,
|
|
1360
1604
|
lastTs
|
|
@@ -1363,9 +1607,9 @@ var ReactionsService = class {
|
|
|
1363
1607
|
}
|
|
1364
1608
|
this.lastRemoteTs.set(parsed.sender.id, parsed.ts);
|
|
1365
1609
|
applyIncomingReaction(parsed);
|
|
1366
|
-
|
|
1610
|
+
logger7.debug("Reaction received", { emoji: parsed.payload.emoji });
|
|
1367
1611
|
} catch (error) {
|
|
1368
|
-
|
|
1612
|
+
logger7.error("Error parsing incoming reaction", error);
|
|
1369
1613
|
}
|
|
1370
1614
|
}
|
|
1371
1615
|
isValidEnvelope(e) {
|
|
@@ -1387,7 +1631,7 @@ var ReactionsService = class {
|
|
|
1387
1631
|
return sender;
|
|
1388
1632
|
}
|
|
1389
1633
|
};
|
|
1390
|
-
var
|
|
1634
|
+
var logger8 = createLogger("reactions:hook");
|
|
1391
1635
|
function useReactions() {
|
|
1392
1636
|
const service = useFeatureService("reactions");
|
|
1393
1637
|
const reactions = useReactionsStore((state) => state.reactions);
|
|
@@ -1406,7 +1650,7 @@ function useReactions() {
|
|
|
1406
1650
|
const sendReaction = useCallback(
|
|
1407
1651
|
async (emoji) => {
|
|
1408
1652
|
if (!service) {
|
|
1409
|
-
|
|
1653
|
+
logger8.error("Cannot send reaction: service not ready");
|
|
1410
1654
|
return;
|
|
1411
1655
|
}
|
|
1412
1656
|
return service.sendReaction(emoji);
|
|
@@ -1424,248 +1668,6 @@ function useReactions() {
|
|
|
1424
1668
|
isReady: !!service
|
|
1425
1669
|
};
|
|
1426
1670
|
}
|
|
1427
|
-
var defaultState4 = {
|
|
1428
|
-
spotlightedUser: null,
|
|
1429
|
-
isSpotlighted: false
|
|
1430
|
-
};
|
|
1431
|
-
var useSpotlightStore = create()(
|
|
1432
|
-
immer((set, get) => ({
|
|
1433
|
-
...defaultState4,
|
|
1434
|
-
spotlight: (participantId, info) => set((state) => {
|
|
1435
|
-
state.spotlightedUser = {
|
|
1436
|
-
participantId,
|
|
1437
|
-
ts: Date.now(),
|
|
1438
|
-
...info && { info }
|
|
1439
|
-
};
|
|
1440
|
-
state.isSpotlighted = true;
|
|
1441
|
-
}),
|
|
1442
|
-
unspotlight: () => set((state) => {
|
|
1443
|
-
state.spotlightedUser = null;
|
|
1444
|
-
state.isSpotlighted = false;
|
|
1445
|
-
}),
|
|
1446
|
-
getSpotlightedUser: () => get().spotlightedUser,
|
|
1447
|
-
clear: () => set(() => ({
|
|
1448
|
-
spotlightedUser: null,
|
|
1449
|
-
isSpotlighted: false
|
|
1450
|
-
}))
|
|
1451
|
-
}))
|
|
1452
|
-
);
|
|
1453
|
-
function applyIncomingSpotlight(envelope) {
|
|
1454
|
-
const { spotlight, unspotlight } = useSpotlightStore.getState();
|
|
1455
|
-
switch (envelope.payload.action) {
|
|
1456
|
-
case "spotlight": {
|
|
1457
|
-
spotlight(envelope.payload.targetId, envelope.payload.info);
|
|
1458
|
-
break;
|
|
1459
|
-
}
|
|
1460
|
-
case "unspotlight": {
|
|
1461
|
-
unspotlight();
|
|
1462
|
-
break;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// src/channel/spotlight/service.ts
|
|
1468
|
-
var logger7 = createLogger("spotlight");
|
|
1469
|
-
var SpotlightService = class {
|
|
1470
|
-
constructor(room) {
|
|
1471
|
-
this.isSubscribed = false;
|
|
1472
|
-
this.room = room;
|
|
1473
|
-
}
|
|
1474
|
-
isRoomReady() {
|
|
1475
|
-
if (!this.room) {
|
|
1476
|
-
logger7.warn("Room not initialized");
|
|
1477
|
-
return false;
|
|
1478
|
-
}
|
|
1479
|
-
if (this.room.state !== ConnectionState.Connected) {
|
|
1480
|
-
logger7.warn("Room not connected", { state: this.room.state });
|
|
1481
|
-
return false;
|
|
1482
|
-
}
|
|
1483
|
-
if (!this.room.localParticipant) {
|
|
1484
|
-
logger7.warn("Local participant not available");
|
|
1485
|
-
return false;
|
|
1486
|
-
}
|
|
1487
|
-
return true;
|
|
1488
|
-
}
|
|
1489
|
-
subscribe() {
|
|
1490
|
-
if (this.isSubscribed) return;
|
|
1491
|
-
this.room.registerTextStreamHandler("spotlight:v1", async (reader) => {
|
|
1492
|
-
try {
|
|
1493
|
-
const text = await reader.readAll();
|
|
1494
|
-
this.handleIncoming(text);
|
|
1495
|
-
} catch (err) {
|
|
1496
|
-
logger7.error("Error reading spotlight stream", err);
|
|
1497
|
-
}
|
|
1498
|
-
});
|
|
1499
|
-
this.isSubscribed = true;
|
|
1500
|
-
logger7.info("SpotlightService subscribed");
|
|
1501
|
-
}
|
|
1502
|
-
unsubscribe() {
|
|
1503
|
-
this.isSubscribed = false;
|
|
1504
|
-
logger7.info("SpotlightService unsubscribed");
|
|
1505
|
-
}
|
|
1506
|
-
getLocalParticipantId() {
|
|
1507
|
-
return this.room.localParticipant.identity;
|
|
1508
|
-
}
|
|
1509
|
-
async spotlight(targetId, info) {
|
|
1510
|
-
if (!this.isRoomReady()) {
|
|
1511
|
-
useRtcStore.getState().addError({
|
|
1512
|
-
code: "SPOTLIGHT_ROOM_NOT_READY",
|
|
1513
|
-
message: "Cannot spotlight: room not connected",
|
|
1514
|
-
timestamp: Date.now()
|
|
1515
|
-
});
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
const senderInfo = this.getSenderInfo();
|
|
1519
|
-
const previousSpotlighted = useSpotlightStore.getState().getSpotlightedUser();
|
|
1520
|
-
useSpotlightStore.getState().spotlight(targetId, info);
|
|
1521
|
-
try {
|
|
1522
|
-
const envelope = {
|
|
1523
|
-
v: 1,
|
|
1524
|
-
kind: "spotlight",
|
|
1525
|
-
roomId: this.room.name,
|
|
1526
|
-
ts: Date.now(),
|
|
1527
|
-
sender: senderInfo,
|
|
1528
|
-
payload: {
|
|
1529
|
-
action: "spotlight",
|
|
1530
|
-
targetId,
|
|
1531
|
-
info
|
|
1532
|
-
}
|
|
1533
|
-
};
|
|
1534
|
-
await this.room.localParticipant.sendText(JSON.stringify(envelope), {
|
|
1535
|
-
topic: "spotlight:v1"
|
|
1536
|
-
});
|
|
1537
|
-
logger7.info("User spotlighted", { targetId });
|
|
1538
|
-
} catch (error) {
|
|
1539
|
-
logger7.error("Failed to spotlight user", error);
|
|
1540
|
-
if (previousSpotlighted) {
|
|
1541
|
-
useSpotlightStore.getState().spotlight(
|
|
1542
|
-
previousSpotlighted.participantId,
|
|
1543
|
-
previousSpotlighted.info
|
|
1544
|
-
);
|
|
1545
|
-
} else {
|
|
1546
|
-
useSpotlightStore.getState().unspotlight();
|
|
1547
|
-
}
|
|
1548
|
-
useRtcStore.getState().addError({
|
|
1549
|
-
code: "SPOTLIGHT_SEND_FAILED",
|
|
1550
|
-
message: error instanceof Error ? error.message : "Failed to spotlight user",
|
|
1551
|
-
timestamp: Date.now()
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
async unspotlight() {
|
|
1556
|
-
if (!this.isRoomReady()) {
|
|
1557
|
-
useRtcStore.getState().addError({
|
|
1558
|
-
code: "UNSPOTLIGHT_ROOM_NOT_READY",
|
|
1559
|
-
message: "Cannot unspotlight: room not connected",
|
|
1560
|
-
timestamp: Date.now()
|
|
1561
|
-
});
|
|
1562
|
-
return;
|
|
1563
|
-
}
|
|
1564
|
-
const senderInfo = this.getSenderInfo();
|
|
1565
|
-
const currentSpotlighted = useSpotlightStore.getState().getSpotlightedUser();
|
|
1566
|
-
if (!currentSpotlighted) {
|
|
1567
|
-
logger7.debug("No user is currently spotlighted");
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
const previousSpotlighted = { ...currentSpotlighted };
|
|
1571
|
-
useSpotlightStore.getState().unspotlight();
|
|
1572
|
-
try {
|
|
1573
|
-
const envelope = {
|
|
1574
|
-
v: 1,
|
|
1575
|
-
kind: "spotlight",
|
|
1576
|
-
roomId: this.room.name,
|
|
1577
|
-
ts: Date.now(),
|
|
1578
|
-
sender: senderInfo,
|
|
1579
|
-
payload: {
|
|
1580
|
-
action: "unspotlight",
|
|
1581
|
-
targetId: previousSpotlighted.participantId
|
|
1582
|
-
}
|
|
1583
|
-
};
|
|
1584
|
-
await this.room.localParticipant.sendText(JSON.stringify(envelope), {
|
|
1585
|
-
topic: "spotlight:v1"
|
|
1586
|
-
});
|
|
1587
|
-
logger7.info("User unspotlighted");
|
|
1588
|
-
} catch (error) {
|
|
1589
|
-
logger7.error("Failed to unspotlight user", error);
|
|
1590
|
-
useSpotlightStore.getState().spotlight(previousSpotlighted.participantId, previousSpotlighted.info);
|
|
1591
|
-
useRtcStore.getState().addError({
|
|
1592
|
-
code: "UNSPOTLIGHT_SEND_FAILED",
|
|
1593
|
-
message: error instanceof Error ? error.message : "Failed to unspotlight user",
|
|
1594
|
-
timestamp: Date.now()
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
handleIncoming(text) {
|
|
1599
|
-
try {
|
|
1600
|
-
const parsed = JSON.parse(text);
|
|
1601
|
-
if (!this.isValidEnvelope(parsed)) {
|
|
1602
|
-
logger7.warn("Invalid spotlight envelope received", parsed);
|
|
1603
|
-
return;
|
|
1604
|
-
}
|
|
1605
|
-
if (parsed.sender.id === this.getLocalParticipantId()) {
|
|
1606
|
-
logger7.debug("Ignoring self-echo spotlight message");
|
|
1607
|
-
return;
|
|
1608
|
-
}
|
|
1609
|
-
applyIncomingSpotlight(parsed);
|
|
1610
|
-
logger7.debug("Spotlight message received", {
|
|
1611
|
-
action: parsed.payload.action,
|
|
1612
|
-
targetId: parsed.payload.targetId
|
|
1613
|
-
});
|
|
1614
|
-
} catch (error) {
|
|
1615
|
-
logger7.error("Error parsing incoming spotlight message", error);
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
isValidEnvelope(e) {
|
|
1619
|
-
return e && e.v === 1 && e.kind === "spotlight" && typeof e.roomId === "string" && e.roomId === this.room.name && typeof e.ts === "number" && e.ts > 0 && typeof e.sender?.id === "string" && typeof e.payload?.action === "string" && ["spotlight", "unspotlight"].includes(e.payload.action) && typeof e.payload?.targetId === "string";
|
|
1620
|
-
}
|
|
1621
|
-
getSenderInfo() {
|
|
1622
|
-
const localParticipant = this.room.localParticipant;
|
|
1623
|
-
const sender = {
|
|
1624
|
-
id: localParticipant.identity
|
|
1625
|
-
};
|
|
1626
|
-
if (localParticipant.metadata) {
|
|
1627
|
-
try {
|
|
1628
|
-
sender.info = JSON.parse(
|
|
1629
|
-
localParticipant.metadata
|
|
1630
|
-
);
|
|
1631
|
-
} catch {
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return sender;
|
|
1635
|
-
}
|
|
1636
|
-
};
|
|
1637
|
-
var logger8 = createLogger("spotlight:hook");
|
|
1638
|
-
function useSpotlight() {
|
|
1639
|
-
const service = useFeatureService("spotlight");
|
|
1640
|
-
const getSpotlightedUser = useSpotlightStore(
|
|
1641
|
-
(state) => state.getSpotlightedUser
|
|
1642
|
-
);
|
|
1643
|
-
const isSpotlighted = useSpotlightStore((state) => state.isSpotlighted);
|
|
1644
|
-
const spotlight = useCallback(
|
|
1645
|
-
async (targetId, info) => {
|
|
1646
|
-
if (!service) {
|
|
1647
|
-
logger8.error("Cannot spotlight: service not ready");
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
return service.spotlight(targetId, info);
|
|
1651
|
-
},
|
|
1652
|
-
[service]
|
|
1653
|
-
);
|
|
1654
|
-
const unspotlight = useCallback(async () => {
|
|
1655
|
-
if (!service) {
|
|
1656
|
-
logger8.error("Cannot unspotlight: service not ready");
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
return service.unspotlight();
|
|
1660
|
-
}, [service]);
|
|
1661
|
-
return {
|
|
1662
|
-
spotlight,
|
|
1663
|
-
unspotlight,
|
|
1664
|
-
getSpotlightedUser,
|
|
1665
|
-
isSpotlighted,
|
|
1666
|
-
isReady: !!service
|
|
1667
|
-
};
|
|
1668
|
-
}
|
|
1669
1671
|
|
|
1670
1672
|
// src/channel/registry.ts
|
|
1671
1673
|
var FEATURES = {
|