nixamp 0.10.1 → 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 +6 -0
- package/dist/directory.js +17 -4
- package/dist/durable.d.ts +2 -0
- package/dist/durable.js +27 -3
- package/package.json +1 -1
- package/src/directory.ts +24 -4
- package/src/durable.ts +27 -3
- 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;
|
package/dist/directory.js
CHANGED
|
@@ -129,8 +129,14 @@ export class Directory {
|
|
|
129
129
|
*/
|
|
130
130
|
seedEnded(items) {
|
|
131
131
|
for (const item of items) {
|
|
132
|
-
if (!this.ended.has(item.id) && !this.items.has(item.id))
|
|
132
|
+
if (!this.ended.has(item.id) && !this.items.has(item.id)) {
|
|
133
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
|
+
}
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
142
|
constructor(ttl = TTL_MS, now = Date.now,
|
|
@@ -186,7 +192,11 @@ export class Directory {
|
|
|
186
192
|
// channel that was gone for a heartbeat -- a server restarting puts
|
|
187
193
|
// its channels back a moment after it announces -- comes back to the
|
|
188
194
|
// code people were given, not a new one.
|
|
189
|
-
{
|
|
195
|
+
{
|
|
196
|
+
...(previously && "channelCodes" in previously ? previously.channelCodes ?? {} : {}),
|
|
197
|
+
...(this.channelHistory.get(id) ?? {}),
|
|
198
|
+
...(existing?.channelCodes ?? {}),
|
|
199
|
+
}, id),
|
|
190
200
|
updatedAt: this.now(),
|
|
191
201
|
// A stream that never stopped keeps its original start. One that did
|
|
192
202
|
// starts again now, because that is what a caller is being told about.
|
|
@@ -204,7 +214,7 @@ export class Directory {
|
|
|
204
214
|
if (item !== undefined)
|
|
205
215
|
this.remember(item);
|
|
206
216
|
this.items.delete(id);
|
|
207
|
-
|
|
217
|
+
// The channel history stays with the ended record, and goes when it goes.
|
|
208
218
|
}
|
|
209
219
|
list() {
|
|
210
220
|
this.sweep();
|
|
@@ -322,6 +332,9 @@ export class Directory {
|
|
|
322
332
|
nowPlaying: item.nowPlaying,
|
|
323
333
|
startedAt: item.startedAt,
|
|
324
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 },
|
|
325
338
|
};
|
|
326
339
|
this.ended.set(item.id, record);
|
|
327
340
|
this.mirror?.save(record);
|
|
@@ -344,13 +357,13 @@ export class Directory {
|
|
|
344
357
|
if (item.updatedAt < cutoff) {
|
|
345
358
|
this.remember(item);
|
|
346
359
|
this.items.delete(id);
|
|
347
|
-
this.channelHistory.delete(id);
|
|
348
360
|
}
|
|
349
361
|
}
|
|
350
362
|
const forget = this.now() - ENDED_TTL_MS;
|
|
351
363
|
for (const [id, item] of this.ended) {
|
|
352
364
|
if (item.endedAt < forget) {
|
|
353
365
|
this.ended.delete(id);
|
|
366
|
+
this.channelHistory.delete(id);
|
|
354
367
|
this.mirror?.drop(id);
|
|
355
368
|
}
|
|
356
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/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. */
|
|
@@ -237,7 +243,14 @@ export class Directory {
|
|
|
237
243
|
*/
|
|
238
244
|
seedEnded(items: readonly Ended[]): void {
|
|
239
245
|
for (const item of items) {
|
|
240
|
-
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
|
+
}
|
|
241
254
|
}
|
|
242
255
|
}
|
|
243
256
|
|
|
@@ -297,7 +310,11 @@ export class Directory {
|
|
|
297
310
|
// channel that was gone for a heartbeat -- a server restarting puts
|
|
298
311
|
// its channels back a moment after it announces -- comes back to the
|
|
299
312
|
// code people were given, not a new one.
|
|
300
|
-
{
|
|
313
|
+
{
|
|
314
|
+
...(previously && "channelCodes" in previously ? previously.channelCodes ?? {} : {}),
|
|
315
|
+
...(this.channelHistory.get(id) ?? {}),
|
|
316
|
+
...(existing?.channelCodes ?? {}),
|
|
317
|
+
},
|
|
301
318
|
id,
|
|
302
319
|
),
|
|
303
320
|
updatedAt: this.now(),
|
|
@@ -316,7 +333,7 @@ export class Directory {
|
|
|
316
333
|
const item = this.items.get(id);
|
|
317
334
|
if (item !== undefined) this.remember(item);
|
|
318
335
|
this.items.delete(id);
|
|
319
|
-
|
|
336
|
+
// The channel history stays with the ended record, and goes when it goes.
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
list(): Listing[] {
|
|
@@ -435,6 +452,9 @@ export class Directory {
|
|
|
435
452
|
nowPlaying: item.nowPlaying,
|
|
436
453
|
startedAt: item.startedAt,
|
|
437
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 },
|
|
438
458
|
};
|
|
439
459
|
this.ended.set(item.id, record);
|
|
440
460
|
this.mirror?.save(record);
|
|
@@ -457,13 +477,13 @@ export class Directory {
|
|
|
457
477
|
if (item.updatedAt < cutoff) {
|
|
458
478
|
this.remember(item);
|
|
459
479
|
this.items.delete(id);
|
|
460
|
-
this.channelHistory.delete(id);
|
|
461
480
|
}
|
|
462
481
|
}
|
|
463
482
|
const forget = this.now() - ENDED_TTL_MS;
|
|
464
483
|
for (const [id, item] of this.ended) {
|
|
465
484
|
if (item.endedAt < forget) {
|
|
466
485
|
this.ended.delete(id);
|
|
486
|
+
this.channelHistory.delete(id);
|
|
467
487
|
this.mirror?.drop(id);
|
|
468
488
|
}
|
|
469
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/web/dist/sw.js
CHANGED