nixamp 0.10.0 → 0.10.2
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/dist/directory.d.ts +8 -0
- package/dist/directory.js +30 -3
- package/dist/durable.d.ts +2 -0
- package/dist/durable.js +27 -3
- package/dist/server.js +6 -0
- package/package.json +1 -1
- package/src/directory.ts +36 -3
- package/src/durable.ts +27 -3
- package/src/server.ts +6 -0
- package/web/dist/sw.js +1 -1
package/dist/directory.d.ts
CHANGED
|
@@ -100,6 +100,12 @@ export interface Ended {
|
|
|
100
100
|
startedAt: number;
|
|
101
101
|
/** The last heartbeat we saw, which is as close to "ended" as we can know. */
|
|
102
102
|
endedAt: number;
|
|
103
|
+
/**
|
|
104
|
+
* The codes its channels had, so a server that stops and comes back -- an
|
|
105
|
+
* update restarts the daemon, and the daemon withdraws its listing on the
|
|
106
|
+
* way down -- puts each channel back under the code people were given.
|
|
107
|
+
*/
|
|
108
|
+
channelCodes?: Record<string, string>;
|
|
103
109
|
}
|
|
104
110
|
/** How long an ended stream is still worth telling a caller about. */
|
|
105
111
|
export declare const ENDED_TTL_MS: number;
|
|
@@ -149,6 +155,8 @@ export declare class Directory {
|
|
|
149
155
|
*/
|
|
150
156
|
private readonly onLive;
|
|
151
157
|
private readonly items;
|
|
158
|
+
/** Every code a channel has had on each listing, so a returning channel keeps its own. */
|
|
159
|
+
private readonly channelHistory;
|
|
152
160
|
/** Streams that stopped, so the phone line can say when. */
|
|
153
161
|
private readonly ended;
|
|
154
162
|
private sequence;
|
package/dist/directory.js
CHANGED
|
@@ -104,6 +104,8 @@ export class Directory {
|
|
|
104
104
|
randomCode;
|
|
105
105
|
onLive;
|
|
106
106
|
items = new Map();
|
|
107
|
+
/** Every code a channel has had on each listing, so a returning channel keeps its own. */
|
|
108
|
+
channelHistory = new Map();
|
|
107
109
|
/** Streams that stopped, so the phone line can say when. */
|
|
108
110
|
ended = new Map();
|
|
109
111
|
sequence = 0;
|
|
@@ -127,8 +129,14 @@ export class Directory {
|
|
|
127
129
|
*/
|
|
128
130
|
seedEnded(items) {
|
|
129
131
|
for (const item of items) {
|
|
130
|
-
if (!this.ended.has(item.id) && !this.items.has(item.id))
|
|
132
|
+
if (!this.ended.has(item.id) && !this.items.has(item.id)) {
|
|
131
133
|
this.ended.set(item.id, item);
|
|
134
|
+
// Its channels' codes are reserved too, so a server coming back after
|
|
135
|
+
// the directory restarted still finds them its own.
|
|
136
|
+
if (item.channelCodes && Object.keys(item.channelCodes).length > 0) {
|
|
137
|
+
this.channelHistory.set(item.id, { ...(this.channelHistory.get(item.id) ?? {}), ...item.channelCodes });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
constructor(ttl = TTL_MS, now = Date.now,
|
|
@@ -179,13 +187,23 @@ export class Directory {
|
|
|
179
187
|
// listing always meant, and no channels is the honest empty list.
|
|
180
188
|
playing: announcement.playing ?? true,
|
|
181
189
|
channels: announcement.channels ?? [],
|
|
182
|
-
channelCodes: this.codesFor(announcement.channels ?? [],
|
|
190
|
+
channelCodes: this.codesFor(announcement.channels ?? [],
|
|
191
|
+
// What each channel has been called before, on this listing: a
|
|
192
|
+
// channel that was gone for a heartbeat -- a server restarting puts
|
|
193
|
+
// its channels back a moment after it announces -- comes back to the
|
|
194
|
+
// code people were given, not a new one.
|
|
195
|
+
{
|
|
196
|
+
...(previously && "channelCodes" in previously ? previously.channelCodes ?? {} : {}),
|
|
197
|
+
...(this.channelHistory.get(id) ?? {}),
|
|
198
|
+
...(existing?.channelCodes ?? {}),
|
|
199
|
+
}, id),
|
|
183
200
|
updatedAt: this.now(),
|
|
184
201
|
// A stream that never stopped keeps its original start. One that did
|
|
185
202
|
// starts again now, because that is what a caller is being told about.
|
|
186
203
|
startedAt: existing?.startedAt ?? this.now(),
|
|
187
204
|
};
|
|
188
205
|
this.items.set(id, listing);
|
|
206
|
+
this.channelHistory.set(id, { ...(this.channelHistory.get(id) ?? {}), ...listing.channelCodes });
|
|
189
207
|
// The transition, not the heartbeat: existing means it was already live.
|
|
190
208
|
if (existing === undefined)
|
|
191
209
|
this.onLive(listing);
|
|
@@ -196,6 +214,7 @@ export class Directory {
|
|
|
196
214
|
if (item !== undefined)
|
|
197
215
|
this.remember(item);
|
|
198
216
|
this.items.delete(id);
|
|
217
|
+
// The channel history stays with the ended record, and goes when it goes.
|
|
199
218
|
}
|
|
200
219
|
list() {
|
|
201
220
|
this.sweep();
|
|
@@ -250,7 +269,11 @@ export class Directory {
|
|
|
250
269
|
for (const item of this.items.values()) {
|
|
251
270
|
if (item.code === code)
|
|
252
271
|
return true;
|
|
253
|
-
|
|
272
|
+
}
|
|
273
|
+
// Every code a channel has ever had on a listing that is still up is
|
|
274
|
+
// that channel's to come back to, on every listing but the one asking.
|
|
275
|
+
for (const [id, codes] of this.channelHistory) {
|
|
276
|
+
if (id !== own && Object.values(codes).includes(code))
|
|
254
277
|
return true;
|
|
255
278
|
}
|
|
256
279
|
return [...this.ended.values()].some((i) => i.code === code);
|
|
@@ -309,6 +332,9 @@ export class Directory {
|
|
|
309
332
|
nowPlaying: item.nowPlaying,
|
|
310
333
|
startedAt: item.startedAt,
|
|
311
334
|
endedAt: item.updatedAt,
|
|
335
|
+
// Every code its channels have had, not only the ones on at the end:
|
|
336
|
+
// a restart announces before its channels are back.
|
|
337
|
+
channelCodes: { ...(this.channelHistory.get(item.id) ?? {}), ...item.channelCodes },
|
|
312
338
|
};
|
|
313
339
|
this.ended.set(item.id, record);
|
|
314
340
|
this.mirror?.save(record);
|
|
@@ -337,6 +363,7 @@ export class Directory {
|
|
|
337
363
|
for (const [id, item] of this.ended) {
|
|
338
364
|
if (item.endedAt < forget) {
|
|
339
365
|
this.ended.delete(id);
|
|
366
|
+
this.channelHistory.delete(id);
|
|
340
367
|
this.mirror?.drop(id);
|
|
341
368
|
}
|
|
342
369
|
}
|
package/dist/durable.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface StoredEnded {
|
|
|
34
34
|
nowPlaying: string;
|
|
35
35
|
startedAt: number;
|
|
36
36
|
endedAt: number;
|
|
37
|
+
/** Each channel's phone code, by name, so a server coming back keeps them. */
|
|
38
|
+
channelCodes?: Record<string, string>;
|
|
37
39
|
}
|
|
38
40
|
export declare class Durable {
|
|
39
41
|
private readonly db;
|
package/dist/durable.js
CHANGED
|
@@ -9,6 +9,7 @@ const SCHEMA = `
|
|
|
9
9
|
started_at BIGINT NOT NULL,
|
|
10
10
|
ended_at BIGINT NOT NULL
|
|
11
11
|
);
|
|
12
|
+
ALTER TABLE ended_streams ADD COLUMN IF NOT EXISTS channel_codes JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
12
13
|
CREATE INDEX IF NOT EXISTS ended_streams_code ON ended_streams (code);
|
|
13
14
|
|
|
14
15
|
CREATE TABLE IF NOT EXISTS stream_reminders (
|
|
@@ -18,6 +19,26 @@ const SCHEMA = `
|
|
|
18
19
|
PRIMARY KEY (code, phone)
|
|
19
20
|
);
|
|
20
21
|
`;
|
|
22
|
+
/** A JSONB column, as pg hands it back: an object already, or a string on an odd driver. */
|
|
23
|
+
function codesFrom(value) {
|
|
24
|
+
let parsed = value;
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(value);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
34
|
+
return {};
|
|
35
|
+
const codes = {};
|
|
36
|
+
for (const [name, code] of Object.entries(parsed)) {
|
|
37
|
+
if (typeof code === "string" && /^\d{6}$/.test(code))
|
|
38
|
+
codes[name] = code;
|
|
39
|
+
}
|
|
40
|
+
return codes;
|
|
41
|
+
}
|
|
21
42
|
export class Durable {
|
|
22
43
|
db;
|
|
23
44
|
onEvent;
|
|
@@ -48,8 +69,8 @@ export class Durable {
|
|
|
48
69
|
}
|
|
49
70
|
async saveEnded(stream) {
|
|
50
71
|
await this.quietly("an ended stream", () => this.db.query(`INSERT INTO ended_streams
|
|
51
|
-
(id, code, name, owner_id, url, now_playing, started_at, ended_at)
|
|
52
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
72
|
+
(id, code, name, owner_id, url, now_playing, started_at, ended_at, channel_codes)
|
|
73
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb)
|
|
53
74
|
ON CONFLICT (id) DO UPDATE
|
|
54
75
|
SET code = EXCLUDED.code,
|
|
55
76
|
name = EXCLUDED.name,
|
|
@@ -57,7 +78,8 @@ export class Durable {
|
|
|
57
78
|
url = EXCLUDED.url,
|
|
58
79
|
now_playing = EXCLUDED.now_playing,
|
|
59
80
|
started_at = EXCLUDED.started_at,
|
|
60
|
-
ended_at = EXCLUDED.ended_at
|
|
81
|
+
ended_at = EXCLUDED.ended_at,
|
|
82
|
+
channel_codes = EXCLUDED.channel_codes`, [
|
|
61
83
|
stream.id,
|
|
62
84
|
stream.code,
|
|
63
85
|
stream.name,
|
|
@@ -66,6 +88,7 @@ export class Durable {
|
|
|
66
88
|
stream.nowPlaying,
|
|
67
89
|
stream.startedAt,
|
|
68
90
|
stream.endedAt,
|
|
91
|
+
JSON.stringify(stream.channelCodes ?? {}),
|
|
69
92
|
]));
|
|
70
93
|
}
|
|
71
94
|
/** A stream that came back, or one old enough to forget. */
|
|
@@ -88,6 +111,7 @@ export class Durable {
|
|
|
88
111
|
// nothing like a number.
|
|
89
112
|
startedAt: Number(r["started_at"] ?? 0),
|
|
90
113
|
endedAt: Number(r["ended_at"] ?? 0),
|
|
114
|
+
channelCodes: codesFrom(r["channel_codes"]),
|
|
91
115
|
}));
|
|
92
116
|
}
|
|
93
117
|
catch (error) {
|
package/dist/server.js
CHANGED
|
@@ -2256,6 +2256,9 @@ export function createHandler(engine, options) {
|
|
|
2256
2256
|
return;
|
|
2257
2257
|
}
|
|
2258
2258
|
options.channels.ephemeral(channelId);
|
|
2259
|
+
// Told to the directory now, so the room code arrives with the
|
|
2260
|
+
// channel rather than at the next heartbeat, ninety seconds on.
|
|
2261
|
+
void options.live?.announce?.();
|
|
2259
2262
|
}
|
|
2260
2263
|
json(response, 200, { kind: "live", channel: channelId, name: entry.title });
|
|
2261
2264
|
return;
|
|
@@ -2354,6 +2357,9 @@ export function createHandler(engine, options) {
|
|
|
2354
2357
|
return;
|
|
2355
2358
|
}
|
|
2356
2359
|
options.channels.ephemeral(channelId);
|
|
2360
|
+
// Told to the directory now, so the room code arrives with the
|
|
2361
|
+
// channel rather than at the next heartbeat, ninety seconds on.
|
|
2362
|
+
void options.live?.announce?.();
|
|
2357
2363
|
}
|
|
2358
2364
|
json(response, 200, shownLink(channelId, resolved));
|
|
2359
2365
|
return;
|
package/package.json
CHANGED
package/src/directory.ts
CHANGED
|
@@ -105,6 +105,12 @@ export interface Ended {
|
|
|
105
105
|
startedAt: number;
|
|
106
106
|
/** The last heartbeat we saw, which is as close to "ended" as we can know. */
|
|
107
107
|
endedAt: number;
|
|
108
|
+
/**
|
|
109
|
+
* The codes its channels had, so a server that stops and comes back -- an
|
|
110
|
+
* update restarts the daemon, and the daemon withdraws its listing on the
|
|
111
|
+
* way down -- puts each channel back under the code people were given.
|
|
112
|
+
*/
|
|
113
|
+
channelCodes?: Record<string, string>;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
/** How long an ended stream is still worth telling a caller about. */
|
|
@@ -210,6 +216,8 @@ export function parseAnnouncement(input: unknown): Announcement | null {
|
|
|
210
216
|
*/
|
|
211
217
|
export class Directory {
|
|
212
218
|
private readonly items = new Map<string, Listing>();
|
|
219
|
+
/** Every code a channel has had on each listing, so a returning channel keeps its own. */
|
|
220
|
+
private readonly channelHistory = new Map<string, Record<string, string>>();
|
|
213
221
|
/** Streams that stopped, so the phone line can say when. */
|
|
214
222
|
private readonly ended = new Map<string, Ended>();
|
|
215
223
|
private sequence = 0;
|
|
@@ -235,7 +243,14 @@ export class Directory {
|
|
|
235
243
|
*/
|
|
236
244
|
seedEnded(items: readonly Ended[]): void {
|
|
237
245
|
for (const item of items) {
|
|
238
|
-
if (!this.ended.has(item.id) && !this.items.has(item.id))
|
|
246
|
+
if (!this.ended.has(item.id) && !this.items.has(item.id)) {
|
|
247
|
+
this.ended.set(item.id, item);
|
|
248
|
+
// Its channels' codes are reserved too, so a server coming back after
|
|
249
|
+
// the directory restarted still finds them its own.
|
|
250
|
+
if (item.channelCodes && Object.keys(item.channelCodes).length > 0) {
|
|
251
|
+
this.channelHistory.set(item.id, { ...(this.channelHistory.get(item.id) ?? {}), ...item.channelCodes });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
239
254
|
}
|
|
240
255
|
}
|
|
241
256
|
|
|
@@ -291,7 +306,15 @@ export class Directory {
|
|
|
291
306
|
channels: announcement.channels ?? [],
|
|
292
307
|
channelCodes: this.codesFor(
|
|
293
308
|
announcement.channels ?? [],
|
|
294
|
-
|
|
309
|
+
// What each channel has been called before, on this listing: a
|
|
310
|
+
// channel that was gone for a heartbeat -- a server restarting puts
|
|
311
|
+
// its channels back a moment after it announces -- comes back to the
|
|
312
|
+
// code people were given, not a new one.
|
|
313
|
+
{
|
|
314
|
+
...(previously && "channelCodes" in previously ? previously.channelCodes ?? {} : {}),
|
|
315
|
+
...(this.channelHistory.get(id) ?? {}),
|
|
316
|
+
...(existing?.channelCodes ?? {}),
|
|
317
|
+
},
|
|
295
318
|
id,
|
|
296
319
|
),
|
|
297
320
|
updatedAt: this.now(),
|
|
@@ -300,6 +323,7 @@ export class Directory {
|
|
|
300
323
|
startedAt: existing?.startedAt ?? this.now(),
|
|
301
324
|
};
|
|
302
325
|
this.items.set(id, listing);
|
|
326
|
+
this.channelHistory.set(id, { ...(this.channelHistory.get(id) ?? {}), ...listing.channelCodes });
|
|
303
327
|
// The transition, not the heartbeat: existing means it was already live.
|
|
304
328
|
if (existing === undefined) this.onLive(listing);
|
|
305
329
|
return listing;
|
|
@@ -309,6 +333,7 @@ export class Directory {
|
|
|
309
333
|
const item = this.items.get(id);
|
|
310
334
|
if (item !== undefined) this.remember(item);
|
|
311
335
|
this.items.delete(id);
|
|
336
|
+
// The channel history stays with the ended record, and goes when it goes.
|
|
312
337
|
}
|
|
313
338
|
|
|
314
339
|
list(): Listing[] {
|
|
@@ -362,7 +387,11 @@ export class Directory {
|
|
|
362
387
|
if (Object.values(besides).includes(code)) return true;
|
|
363
388
|
for (const item of this.items.values()) {
|
|
364
389
|
if (item.code === code) return true;
|
|
365
|
-
|
|
390
|
+
}
|
|
391
|
+
// Every code a channel has ever had on a listing that is still up is
|
|
392
|
+
// that channel's to come back to, on every listing but the one asking.
|
|
393
|
+
for (const [id, codes] of this.channelHistory) {
|
|
394
|
+
if (id !== own && Object.values(codes).includes(code)) return true;
|
|
366
395
|
}
|
|
367
396
|
return [...this.ended.values()].some((i) => i.code === code);
|
|
368
397
|
}
|
|
@@ -423,6 +452,9 @@ export class Directory {
|
|
|
423
452
|
nowPlaying: item.nowPlaying,
|
|
424
453
|
startedAt: item.startedAt,
|
|
425
454
|
endedAt: item.updatedAt,
|
|
455
|
+
// Every code its channels have had, not only the ones on at the end:
|
|
456
|
+
// a restart announces before its channels are back.
|
|
457
|
+
channelCodes: { ...(this.channelHistory.get(item.id) ?? {}), ...item.channelCodes },
|
|
426
458
|
};
|
|
427
459
|
this.ended.set(item.id, record);
|
|
428
460
|
this.mirror?.save(record);
|
|
@@ -451,6 +483,7 @@ export class Directory {
|
|
|
451
483
|
for (const [id, item] of this.ended) {
|
|
452
484
|
if (item.endedAt < forget) {
|
|
453
485
|
this.ended.delete(id);
|
|
486
|
+
this.channelHistory.delete(id);
|
|
454
487
|
this.mirror?.drop(id);
|
|
455
488
|
}
|
|
456
489
|
}
|
package/src/durable.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface StoredEnded {
|
|
|
35
35
|
nowPlaying: string;
|
|
36
36
|
startedAt: number;
|
|
37
37
|
endedAt: number;
|
|
38
|
+
/** Each channel's phone code, by name, so a server coming back keeps them. */
|
|
39
|
+
channelCodes?: Record<string, string>;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
const SCHEMA = `
|
|
@@ -48,6 +50,7 @@ const SCHEMA = `
|
|
|
48
50
|
started_at BIGINT NOT NULL,
|
|
49
51
|
ended_at BIGINT NOT NULL
|
|
50
52
|
);
|
|
53
|
+
ALTER TABLE ended_streams ADD COLUMN IF NOT EXISTS channel_codes JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
51
54
|
CREATE INDEX IF NOT EXISTS ended_streams_code ON ended_streams (code);
|
|
52
55
|
|
|
53
56
|
CREATE TABLE IF NOT EXISTS stream_reminders (
|
|
@@ -58,6 +61,24 @@ const SCHEMA = `
|
|
|
58
61
|
);
|
|
59
62
|
`;
|
|
60
63
|
|
|
64
|
+
/** A JSONB column, as pg hands it back: an object already, or a string on an odd driver. */
|
|
65
|
+
function codesFrom(value: unknown): Record<string, string> {
|
|
66
|
+
let parsed: unknown = value;
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(value);
|
|
70
|
+
} catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
75
|
+
const codes: Record<string, string> = {};
|
|
76
|
+
for (const [name, code] of Object.entries(parsed as Record<string, unknown>)) {
|
|
77
|
+
if (typeof code === "string" && /^\d{6}$/.test(code)) codes[name] = code;
|
|
78
|
+
}
|
|
79
|
+
return codes;
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
export class Durable {
|
|
62
83
|
private ready: Promise<void> | null = null;
|
|
63
84
|
|
|
@@ -91,8 +112,8 @@ export class Durable {
|
|
|
91
112
|
await this.quietly("an ended stream", () =>
|
|
92
113
|
this.db.query(
|
|
93
114
|
`INSERT INTO ended_streams
|
|
94
|
-
(id, code, name, owner_id, url, now_playing, started_at, ended_at)
|
|
95
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
115
|
+
(id, code, name, owner_id, url, now_playing, started_at, ended_at, channel_codes)
|
|
116
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb)
|
|
96
117
|
ON CONFLICT (id) DO UPDATE
|
|
97
118
|
SET code = EXCLUDED.code,
|
|
98
119
|
name = EXCLUDED.name,
|
|
@@ -100,7 +121,8 @@ export class Durable {
|
|
|
100
121
|
url = EXCLUDED.url,
|
|
101
122
|
now_playing = EXCLUDED.now_playing,
|
|
102
123
|
started_at = EXCLUDED.started_at,
|
|
103
|
-
ended_at = EXCLUDED.ended_at
|
|
124
|
+
ended_at = EXCLUDED.ended_at,
|
|
125
|
+
channel_codes = EXCLUDED.channel_codes`,
|
|
104
126
|
[
|
|
105
127
|
stream.id,
|
|
106
128
|
stream.code,
|
|
@@ -110,6 +132,7 @@ export class Durable {
|
|
|
110
132
|
stream.nowPlaying,
|
|
111
133
|
stream.startedAt,
|
|
112
134
|
stream.endedAt,
|
|
135
|
+
JSON.stringify(stream.channelCodes ?? {}),
|
|
113
136
|
],
|
|
114
137
|
),
|
|
115
138
|
);
|
|
@@ -141,6 +164,7 @@ export class Durable {
|
|
|
141
164
|
// nothing like a number.
|
|
142
165
|
startedAt: Number(r["started_at"] ?? 0),
|
|
143
166
|
endedAt: Number(r["ended_at"] ?? 0),
|
|
167
|
+
channelCodes: codesFrom(r["channel_codes"]),
|
|
144
168
|
}));
|
|
145
169
|
} catch (error) {
|
|
146
170
|
this.onEvent(` could not read ended streams: ${(error as Error).message}`);
|
package/src/server.ts
CHANGED
|
@@ -2721,6 +2721,9 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
2721
2721
|
return;
|
|
2722
2722
|
}
|
|
2723
2723
|
options.channels.ephemeral(channelId);
|
|
2724
|
+
// Told to the directory now, so the room code arrives with the
|
|
2725
|
+
// channel rather than at the next heartbeat, ninety seconds on.
|
|
2726
|
+
void options.live?.announce?.();
|
|
2724
2727
|
}
|
|
2725
2728
|
json(response, 200, { kind: "live", channel: channelId, name: entry.title });
|
|
2726
2729
|
return;
|
|
@@ -2826,6 +2829,9 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
2826
2829
|
return;
|
|
2827
2830
|
}
|
|
2828
2831
|
options.channels.ephemeral(channelId);
|
|
2832
|
+
// Told to the directory now, so the room code arrives with the
|
|
2833
|
+
// channel rather than at the next heartbeat, ninety seconds on.
|
|
2834
|
+
void options.live?.announce?.();
|
|
2829
2835
|
}
|
|
2830
2836
|
json(response, 200, shownLink(channelId, resolved));
|
|
2831
2837
|
return;
|
package/web/dist/sw.js
CHANGED