nixamp 0.7.1 → 0.7.3
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/server.js +103 -103
- package/package.json +1 -1
- package/src/server.ts +108 -108
- package/web/dist/assets/{hls-3VKVEQE3-6P5P66VW.js → hls-3VKVEQE3-BirljKil.js} +1 -1
- package/web/dist/assets/index-U2odRmpd.js +1 -0
- package/web/dist/assets/{mpegts-BUbeP1QU.js → mpegts-Dd6YyA19.js} +1 -1
- package/web/dist/assets/{mpegts-LO6RVLD6-CEVDX7YC.js → mpegts-LO6RVLD6-Cl9mowBJ.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/sw.js +5 -5
- package/web/dist/assets/index-0Asn6zxx.js +0 -1
package/dist/server.js
CHANGED
|
@@ -1001,109 +1001,6 @@ export function createHandler(engine, options) {
|
|
|
1001
1001
|
json(response, 404, { error: "no such endpoint" });
|
|
1002
1002
|
return;
|
|
1003
1003
|
}
|
|
1004
|
-
// --- the name other people see ---------------------------------------
|
|
1005
|
-
//
|
|
1006
|
-
// Separate from the address on purpose. The address is a credential and
|
|
1007
|
-
// a way to reach somebody; publishing it in a directory listing or an
|
|
1008
|
-
// invite would be publishing what they log in with.
|
|
1009
|
-
if (path === "/api/v1/me/handle" && options.handles) {
|
|
1010
|
-
const handles = options.handles;
|
|
1011
|
-
const who = await accounts.whoIs(tokenFrom(request.headers));
|
|
1012
|
-
if (who === null) {
|
|
1013
|
-
json(response, 401, { error: "not signed in" });
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
if (request.method === "GET") {
|
|
1017
|
-
json(response, 200, { handle: await handles.of(who.id) });
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
if (request.method === "PUT" || request.method === "POST") {
|
|
1021
|
-
let body;
|
|
1022
|
-
try {
|
|
1023
|
-
body = JSON.parse(await readBody(request));
|
|
1024
|
-
}
|
|
1025
|
-
catch {
|
|
1026
|
-
json(response, 400, { error: "bad JSON" });
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
const claimed = await handles.claim(who.id, body.handle);
|
|
1030
|
-
if (claimed.error) {
|
|
1031
|
-
json(response, 409, { error: claimed.error });
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
json(response, 200, { handle: claimed.handle });
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
json(response, 405, { error: "GET or PUT" });
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
// --- the servers this account runs ----------------------------------
|
|
1041
|
-
//
|
|
1042
|
-
// Kept against the account rather than the machine, so the list reads the
|
|
1043
|
-
// same from the CLI, the PWA and the desktop app -- which is the whole
|
|
1044
|
-
// point: a share link in a terminal you closed is a server you have lost.
|
|
1045
|
-
if ((path === "/api/v1/servers" || path.startsWith("/api/v1/servers/")) && options.servers) {
|
|
1046
|
-
const servers = options.servers;
|
|
1047
|
-
const who = await accounts.whoIs(tokenFrom(request.headers));
|
|
1048
|
-
if (who === null) {
|
|
1049
|
-
json(response, 401, { error: "not signed in" });
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (path === "/api/v1/servers" && request.method === "GET") {
|
|
1053
|
-
json(response, 200, { servers: await servers.list(who.id) });
|
|
1054
|
-
return;
|
|
1055
|
-
}
|
|
1056
|
-
if (path === "/api/v1/servers" && request.method === "POST") {
|
|
1057
|
-
let body;
|
|
1058
|
-
try {
|
|
1059
|
-
body = JSON.parse(await readBody(request));
|
|
1060
|
-
}
|
|
1061
|
-
catch {
|
|
1062
|
-
json(response, 400, { error: "bad JSON" });
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
const made = await servers.add(who, {
|
|
1066
|
-
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1067
|
-
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1068
|
-
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1069
|
-
});
|
|
1070
|
-
if (made === null) {
|
|
1071
|
-
json(response, 422, { error: "that needs an http or https address" });
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
1074
|
-
json(response, 201, { server: made });
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
const id = path.slice("/api/v1/servers/".length);
|
|
1078
|
-
if (id && request.method === "DELETE") {
|
|
1079
|
-
const gone = await servers.remove(who.id, id);
|
|
1080
|
-
json(response, gone ? 200 : 404, gone ? { ok: true } : { error: "no such server" });
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
if (id && (request.method === "PATCH" || request.method === "PUT")) {
|
|
1084
|
-
let body;
|
|
1085
|
-
try {
|
|
1086
|
-
body = JSON.parse(await readBody(request));
|
|
1087
|
-
}
|
|
1088
|
-
catch {
|
|
1089
|
-
json(response, 400, { error: "bad JSON" });
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
const changed = await servers.update(who.id, id, {
|
|
1093
|
-
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1094
|
-
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1095
|
-
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1096
|
-
});
|
|
1097
|
-
if (changed === null) {
|
|
1098
|
-
json(response, 404, { error: "no such server, or a bad address" });
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
json(response, 200, { server: changed });
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
json(response, 405, { error: "GET, POST, PATCH or DELETE" });
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
1004
|
// --- tokens a person made on purpose --------------------------------
|
|
1108
1005
|
if (path === "/api/v1/auth/tokens" || path.startsWith("/api/v1/auth/tokens/")) {
|
|
1109
1006
|
const who = await accounts.whoIs(tokenFrom(request.headers));
|
|
@@ -1220,6 +1117,109 @@ export function createHandler(engine, options) {
|
|
|
1220
1117
|
response.end(JSON.stringify({ account: result.account, token }));
|
|
1221
1118
|
return;
|
|
1222
1119
|
}
|
|
1120
|
+
// --- the name other people see ---------------------------------------
|
|
1121
|
+
//
|
|
1122
|
+
// Separate from the address on purpose. The address is a credential and
|
|
1123
|
+
// a way to reach somebody; publishing it in a directory listing or an
|
|
1124
|
+
// invite would be publishing what they log in with.
|
|
1125
|
+
if (path === "/api/v1/me/handle" && options.handles && options.accounts) {
|
|
1126
|
+
const handles = options.handles;
|
|
1127
|
+
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
1128
|
+
if (who === null) {
|
|
1129
|
+
json(response, 401, { error: "not signed in" });
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (request.method === "GET") {
|
|
1133
|
+
json(response, 200, { handle: await handles.of(who.id) });
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (request.method === "PUT" || request.method === "POST") {
|
|
1137
|
+
let body;
|
|
1138
|
+
try {
|
|
1139
|
+
body = JSON.parse(await readBody(request));
|
|
1140
|
+
}
|
|
1141
|
+
catch {
|
|
1142
|
+
json(response, 400, { error: "bad JSON" });
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const claimed = await handles.claim(who.id, body.handle);
|
|
1146
|
+
if (claimed.error) {
|
|
1147
|
+
json(response, 409, { error: claimed.error });
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
json(response, 200, { handle: claimed.handle });
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
json(response, 405, { error: "GET or PUT" });
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
// --- the servers this account runs ----------------------------------
|
|
1157
|
+
//
|
|
1158
|
+
// Kept against the account rather than the machine, so the list reads the
|
|
1159
|
+
// same from the CLI, the PWA and the desktop app -- which is the whole
|
|
1160
|
+
// point: a share link in a terminal you closed is a server you have lost.
|
|
1161
|
+
if ((path === "/api/v1/servers" || path.startsWith("/api/v1/servers/")) && options.servers && options.accounts) {
|
|
1162
|
+
const servers = options.servers;
|
|
1163
|
+
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
1164
|
+
if (who === null) {
|
|
1165
|
+
json(response, 401, { error: "not signed in" });
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (path === "/api/v1/servers" && request.method === "GET") {
|
|
1169
|
+
json(response, 200, { servers: await servers.list(who.id) });
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (path === "/api/v1/servers" && request.method === "POST") {
|
|
1173
|
+
let body;
|
|
1174
|
+
try {
|
|
1175
|
+
body = JSON.parse(await readBody(request));
|
|
1176
|
+
}
|
|
1177
|
+
catch {
|
|
1178
|
+
json(response, 400, { error: "bad JSON" });
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
const made = await servers.add(who, {
|
|
1182
|
+
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1183
|
+
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1184
|
+
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1185
|
+
});
|
|
1186
|
+
if (made === null) {
|
|
1187
|
+
json(response, 422, { error: "that needs an http or https address" });
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
json(response, 201, { server: made });
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const id = path.slice("/api/v1/servers/".length);
|
|
1194
|
+
if (id && request.method === "DELETE") {
|
|
1195
|
+
const gone = await servers.remove(who.id, id);
|
|
1196
|
+
json(response, gone ? 200 : 404, gone ? { ok: true } : { error: "no such server" });
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (id && (request.method === "PATCH" || request.method === "PUT")) {
|
|
1200
|
+
let body;
|
|
1201
|
+
try {
|
|
1202
|
+
body = JSON.parse(await readBody(request));
|
|
1203
|
+
}
|
|
1204
|
+
catch {
|
|
1205
|
+
json(response, 400, { error: "bad JSON" });
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const changed = await servers.update(who.id, id, {
|
|
1209
|
+
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1210
|
+
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1211
|
+
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1212
|
+
});
|
|
1213
|
+
if (changed === null) {
|
|
1214
|
+
json(response, 404, { error: "no such server, or a bad address" });
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
json(response, 200, { server: changed });
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
json(response, 405, { error: "GET, POST, PATCH or DELETE" });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
1223
|
// --- open directories somebody found -----------------------------------
|
|
1224
1224
|
//
|
|
1225
1225
|
// Its own list, not the stream directory: that one is what is playing now,
|
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -1267,114 +1267,6 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
1267
1267
|
return;
|
|
1268
1268
|
}
|
|
1269
1269
|
|
|
1270
|
-
// --- the name other people see ---------------------------------------
|
|
1271
|
-
//
|
|
1272
|
-
// Separate from the address on purpose. The address is a credential and
|
|
1273
|
-
// a way to reach somebody; publishing it in a directory listing or an
|
|
1274
|
-
// invite would be publishing what they log in with.
|
|
1275
|
-
if (path === "/api/v1/me/handle" && options.handles) {
|
|
1276
|
-
const handles = options.handles;
|
|
1277
|
-
const who = await accounts.whoIs(tokenFrom(request.headers));
|
|
1278
|
-
if (who === null) {
|
|
1279
|
-
json(response, 401, { error: "not signed in" });
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
if (request.method === "GET") {
|
|
1284
|
-
json(response, 200, { handle: await handles.of(who.id) });
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
if (request.method === "PUT" || request.method === "POST") {
|
|
1288
|
-
let body: { handle?: unknown };
|
|
1289
|
-
try {
|
|
1290
|
-
body = JSON.parse(await readBody(request)) as typeof body;
|
|
1291
|
-
} catch {
|
|
1292
|
-
json(response, 400, { error: "bad JSON" });
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1295
|
-
const claimed = await handles.claim(who.id, body.handle);
|
|
1296
|
-
if (claimed.error) {
|
|
1297
|
-
json(response, 409, { error: claimed.error });
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1300
|
-
json(response, 200, { handle: claimed.handle });
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
json(response, 405, { error: "GET or PUT" });
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// --- the servers this account runs ----------------------------------
|
|
1308
|
-
//
|
|
1309
|
-
// Kept against the account rather than the machine, so the list reads the
|
|
1310
|
-
// same from the CLI, the PWA and the desktop app -- which is the whole
|
|
1311
|
-
// point: a share link in a terminal you closed is a server you have lost.
|
|
1312
|
-
if ((path === "/api/v1/servers" || path.startsWith("/api/v1/servers/")) && options.servers) {
|
|
1313
|
-
const servers = options.servers;
|
|
1314
|
-
const who = await accounts.whoIs(tokenFrom(request.headers));
|
|
1315
|
-
if (who === null) {
|
|
1316
|
-
json(response, 401, { error: "not signed in" });
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if (path === "/api/v1/servers" && request.method === "GET") {
|
|
1321
|
-
json(response, 200, { servers: await servers.list(who.id) });
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
if (path === "/api/v1/servers" && request.method === "POST") {
|
|
1326
|
-
let body: { name?: unknown; url?: unknown; key?: unknown };
|
|
1327
|
-
try {
|
|
1328
|
-
body = JSON.parse(await readBody(request)) as typeof body;
|
|
1329
|
-
} catch {
|
|
1330
|
-
json(response, 400, { error: "bad JSON" });
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
const made = await servers.add(who, {
|
|
1334
|
-
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1335
|
-
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1336
|
-
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1337
|
-
});
|
|
1338
|
-
if (made === null) {
|
|
1339
|
-
json(response, 422, { error: "that needs an http or https address" });
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
json(response, 201, { server: made });
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
const id = path.slice("/api/v1/servers/".length);
|
|
1347
|
-
if (id && request.method === "DELETE") {
|
|
1348
|
-
const gone = await servers.remove(who.id, id);
|
|
1349
|
-
json(response, gone ? 200 : 404, gone ? { ok: true } : { error: "no such server" });
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
if (id && (request.method === "PATCH" || request.method === "PUT")) {
|
|
1354
|
-
let body: { name?: unknown; url?: unknown; key?: unknown };
|
|
1355
|
-
try {
|
|
1356
|
-
body = JSON.parse(await readBody(request)) as typeof body;
|
|
1357
|
-
} catch {
|
|
1358
|
-
json(response, 400, { error: "bad JSON" });
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1361
|
-
const changed = await servers.update(who.id, id, {
|
|
1362
|
-
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1363
|
-
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1364
|
-
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1365
|
-
});
|
|
1366
|
-
if (changed === null) {
|
|
1367
|
-
json(response, 404, { error: "no such server, or a bad address" });
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
json(response, 200, { server: changed });
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
json(response, 405, { error: "GET, POST, PATCH or DELETE" });
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
1270
|
// --- tokens a person made on purpose --------------------------------
|
|
1379
1271
|
if (path === "/api/v1/auth/tokens" || path.startsWith("/api/v1/auth/tokens/")) {
|
|
1380
1272
|
const who = await accounts.whoIs(tokenFrom(request.headers));
|
|
@@ -1501,6 +1393,114 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
1501
1393
|
return;
|
|
1502
1394
|
}
|
|
1503
1395
|
|
|
1396
|
+
// --- the name other people see ---------------------------------------
|
|
1397
|
+
//
|
|
1398
|
+
// Separate from the address on purpose. The address is a credential and
|
|
1399
|
+
// a way to reach somebody; publishing it in a directory listing or an
|
|
1400
|
+
// invite would be publishing what they log in with.
|
|
1401
|
+
if (path === "/api/v1/me/handle" && options.handles && options.accounts) {
|
|
1402
|
+
const handles = options.handles;
|
|
1403
|
+
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
1404
|
+
if (who === null) {
|
|
1405
|
+
json(response, 401, { error: "not signed in" });
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (request.method === "GET") {
|
|
1410
|
+
json(response, 200, { handle: await handles.of(who.id) });
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (request.method === "PUT" || request.method === "POST") {
|
|
1414
|
+
let body: { handle?: unknown };
|
|
1415
|
+
try {
|
|
1416
|
+
body = JSON.parse(await readBody(request)) as typeof body;
|
|
1417
|
+
} catch {
|
|
1418
|
+
json(response, 400, { error: "bad JSON" });
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
const claimed = await handles.claim(who.id, body.handle);
|
|
1422
|
+
if (claimed.error) {
|
|
1423
|
+
json(response, 409, { error: claimed.error });
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
json(response, 200, { handle: claimed.handle });
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
json(response, 405, { error: "GET or PUT" });
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// --- the servers this account runs ----------------------------------
|
|
1434
|
+
//
|
|
1435
|
+
// Kept against the account rather than the machine, so the list reads the
|
|
1436
|
+
// same from the CLI, the PWA and the desktop app -- which is the whole
|
|
1437
|
+
// point: a share link in a terminal you closed is a server you have lost.
|
|
1438
|
+
if ((path === "/api/v1/servers" || path.startsWith("/api/v1/servers/")) && options.servers && options.accounts) {
|
|
1439
|
+
const servers = options.servers;
|
|
1440
|
+
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
1441
|
+
if (who === null) {
|
|
1442
|
+
json(response, 401, { error: "not signed in" });
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (path === "/api/v1/servers" && request.method === "GET") {
|
|
1447
|
+
json(response, 200, { servers: await servers.list(who.id) });
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
if (path === "/api/v1/servers" && request.method === "POST") {
|
|
1452
|
+
let body: { name?: unknown; url?: unknown; key?: unknown };
|
|
1453
|
+
try {
|
|
1454
|
+
body = JSON.parse(await readBody(request)) as typeof body;
|
|
1455
|
+
} catch {
|
|
1456
|
+
json(response, 400, { error: "bad JSON" });
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
const made = await servers.add(who, {
|
|
1460
|
+
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1461
|
+
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1462
|
+
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1463
|
+
});
|
|
1464
|
+
if (made === null) {
|
|
1465
|
+
json(response, 422, { error: "that needs an http or https address" });
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
json(response, 201, { server: made });
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const id = path.slice("/api/v1/servers/".length);
|
|
1473
|
+
if (id && request.method === "DELETE") {
|
|
1474
|
+
const gone = await servers.remove(who.id, id);
|
|
1475
|
+
json(response, gone ? 200 : 404, gone ? { ok: true } : { error: "no such server" });
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (id && (request.method === "PATCH" || request.method === "PUT")) {
|
|
1480
|
+
let body: { name?: unknown; url?: unknown; key?: unknown };
|
|
1481
|
+
try {
|
|
1482
|
+
body = JSON.parse(await readBody(request)) as typeof body;
|
|
1483
|
+
} catch {
|
|
1484
|
+
json(response, 400, { error: "bad JSON" });
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
const changed = await servers.update(who.id, id, {
|
|
1488
|
+
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
1489
|
+
...(typeof body.url === "string" ? { url: body.url } : {}),
|
|
1490
|
+
...(typeof body.key === "string" ? { key: body.key } : {}),
|
|
1491
|
+
});
|
|
1492
|
+
if (changed === null) {
|
|
1493
|
+
json(response, 404, { error: "no such server, or a bad address" });
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
json(response, 200, { server: changed });
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
json(response, 405, { error: "GET, POST, PATCH or DELETE" });
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
1504
|
// --- open directories somebody found -----------------------------------
|
|
1505
1505
|
//
|
|
1506
1506
|
// Its own list, not the stream directory: that one is what is playing now,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./index-
|
|
1
|
+
import{t as e}from"./index-U2odRmpd.js";var t=3;async function n(n){let{media:r,src:i,isTv:a}=n,{default:o}=await e(async()=>{let{default:e}=await import(`./hls-n74Cnh8A.js`);return{default:e}},[]);if(!o.isSupported())return n.onError(`This browser cannot play HLS streams.`),{destroy:()=>void 0,levels:()=>[]};let s=new o({...a?{maxBufferLength:60,maxMaxBufferLength:120,backBufferLength:30,liveSyncDurationCount:4}:{backBufferLength:90},enableWorker:!0}),c=0,l=!1;s.on(o.Events.ERROR,(e,r)=>{if(!l&&r.fatal){if(c>=t){n.onError(`This stream kept failing and has been stopped.`),s.destroy();return}switch(c+=1,r.type){case o.ErrorTypes.NETWORK_ERROR:n.onNotice(`Reconnecting…`),s.startLoad();break;case o.ErrorTypes.MEDIA_ERROR:n.onNotice(`Recovering…`),s.recoverMediaError();break;default:n.onError(`This stream could not be played.`),s.destroy()}}}),s.on(o.Events.MANIFEST_PARSED,()=>{l||(n.onNotice(null),n.onReady?.({live:s.levels.length>0&&!Number.isFinite(r.duration),levels:u()}))}),s.on(o.Events.LEVEL_LOADED,(e,t)=>{l||n.onReady?.({live:t.details.live,levels:u()})}),s.on(o.Events.FRAG_BUFFERED,()=>{l||n.onNotice(null)});function u(){return s.levels.map((e,t)=>({index:t,height:e.height||null,bitrate:e.bitrate||null,label:e.height?`${String(e.height)}p`:`${String(Math.round((e.bitrate||0)/1e3))}k`}))}return s.loadSource(i),s.attachMedia(r),{destroy(){l=!0,s.destroy()},levels:u,setLevel(e){s.currentLevel=e},currentLevel:()=>s.autoLevelEnabled?-1:s.currentLevel}}export{n as createHlsEngine};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){if(!Number.isFinite(e)||e<0)return`--:--`;let t=Math.floor(e),n=Math.floor(t/60),r=t%60;return`${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`}function t(e){return e.artist?`${e.artist} — ${e.title}`:e.title}function n(e){let t=e.split(`/`).pop()??e,n=t.lastIndexOf(`.`);return n>0?t.slice(0,n):t}var r=new Set([`mp4`,`webm`,`mkv`,`mov`,`m4v`,`ogv`,`avi`]);function i(e,t=``){if(t.startsWith(`video/`))return!0;if(t.startsWith(`audio/`))return!1;let n=e.lastIndexOf(`.`);return n>0&&r.has(e.slice(n+1).toLowerCase())}var a=`modulepreload`,o=function(e){return`/`+e},s={},c=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),c=i?.nonce||i?.getAttribute(`nonce`);function l(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function u(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=l(t.map(t=>{if(t=o(t,n),t=u(t),t in s)return;s[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:a,r||(i.as=`script`),i.crossOrigin=``,i.href=t,c&&i.setAttribute(`nonce`,c),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},l=[[/\.m3u8$/i,`hls`],[/\.(ts|mts|m2ts|mpegts)$/i,`mpegts`],[/\.(mp3|m4a|aac|oga|ogg|opus|wav|flac)$/i,`audio`],[/\.(mp4|m4v|webm|mov|ogv)$/i,`mp4`]],u=[[/mpegurl/i,`hls`],[/mp2t|mpeg-?ts/i,`mpegts`],[/^audio\//i,`audio`],[/^video\//i,`mp4`]];function d(e){if(e.kind)return e.kind;if(e.mimeType){for(let[t,n]of u)if(t.test(e.mimeType))return n}let t=f(e.src);for(let[e,n]of l)if(e.test(t))return n;return`unknown`}function f(e){try{return new URL(e,`https://placeholder.invalid`).pathname}catch{return e.split(/[?#]/)[0]??e}}function p(e=globalThis,t=null){let n=e.MediaSource!==void 0,r=!1;try{let e=t??(typeof document>`u`?null:document.createElement(`video`));r=e?e.canPlayType(`application/vnd.apple.mpegurl`)!==``||e.canPlayType(`application/x-mpegURL`)!==``:!1}catch{r=!1}return{mediaSource:n,nativeHls:r}}function m(e,t){let n=d(e);switch(n){case`hls`:return t.mediaSource?{engine:`hls`,kind:n}:t.nativeHls?{engine:`native`,kind:n}:{engine:`native`,kind:n,unplayable:`This browser cannot play HLS streams.`};case`mpegts`:return t.mediaSource?{engine:`mpegts`,kind:n}:{engine:`mpegts`,kind:n,unplayable:`This browser cannot play transport streams.`};default:return{engine:`native`,kind:n}}}async function h(e,t){let n=t.capabilities??p(),r=m({src:t.src,...t.kind?{kind:t.kind}:{},...t.mimeType?{mimeType:t.mimeType}:{}},n),i=()=>void 0,a={media:e,src:t.src,isTv:t.isTv??!1,live:t.live??r.kind===`mpegts`,onError:t.onError??i,onNotice:t.onNotice??i,...t.onReady?{onReady:t.onReady}:{}};if(r.unplayable)return t.onError?.(r.unplayable),{destroy:i,engine:r.engine,kind:r.kind,levels:()=>[],unplayable:r.unplayable};let o,s=t.engines?.[r.engine];if(s)o=await s(a);else if(r.engine===`hls`){let{createHlsEngine:e}=await c(async()=>{let{createHlsEngine:e}=await import(`./hls-3VKVEQE3-BirljKil.js`);return{createHlsEngine:e}},[]);o=await e(a)}else if(r.engine===`mpegts`){let{createMpegtsEngine:e}=await c(async()=>{let{createMpegtsEngine:e}=await import(`./mpegts-LO6RVLD6-Cl9mowBJ.js`);return{createMpegtsEngine:e}},[]);o=await e(a,{withCredentials:t.withCredentials??!1,unplayableAdvice:t.unplayableAdvice??``})}else{let{createNativeEngine:e}=await c(async()=>{let{createNativeEngine:e}=await import(`./native-C7JTKWJH-BUyIoj0P.js`);return{createNativeEngine:e}},[]);o=await e(a)}return{destroy:()=>{o.destroy()},engine:r.engine,kind:r.kind,levels:o.levels,...o.setLevel?{setLevel:o.setLevel}:{},...o.currentLevel?{currentLevel:o.currentLevel}:{}}}var g=new Set([`mp3`,`flac`,`ogg`,`oga`,`opus`,`m4a`,`aac`,`wav`,`wma`,`aiff`,`aif`,`alac`,`mp4`,`webm`,`mkv`,`mov`,`m4v`,`ogv`]);function _(e,t=``){if(t.startsWith(`audio/`)||t.startsWith(`video/`))return!0;let n=e.lastIndexOf(`.`);return n>0&&g.has(e.slice(n+1).toLowerCase())}function v(e,t){return e.localeCompare(t,void 0,{numeric:!0,sensitivity:`base`})}function y(e){return e.filter(e=>_(e.name,e.type)).sort((e,t)=>v(b(e),b(t))).map(e=>({title:n(e.name),artist:``,album:x(b(e)),duration:0,url:URL.createObjectURL(e),video:i(e.name,e.type),objectUrl:!0}))}function b(e){return e.webkitRelativePath||e.name}function x(e){let t=e.split(`/`);return t.length>1?t[t.length-2]:``}function ee(e){for(let t of e)t.objectUrl&&URL.revokeObjectURL(t.url)}var S=2048;function C(e){return e===`audio`}var te=class{elements;handlers;attached=null;context=null;analyser=null;wired=new WeakSet;active;frequencies=new Uint8Array;constructor(e,t){this.elements=e,this.handlers=t,this.active=e.audio;for(let t of[e.audio,e.video])t.crossOrigin=`anonymous`,t.addEventListener(`timeupdate`,()=>{t===this.active&&this.handlers.onTime(t.currentTime,Number.isFinite(t.duration)?t.duration:0)}),t.addEventListener(`loadedmetadata`,()=>{t===this.active&&this.handlers.onTime(t.currentTime,Number.isFinite(t.duration)?t.duration:0)}),t.addEventListener(`ended`,()=>{t===this.active&&this.handlers.onEnded()}),t.addEventListener(`play`,()=>{t===this.active&&this.handlers.onState(!0)}),t.addEventListener(`pause`,()=>{t===this.active&&this.handlers.onState(!1)}),t.addEventListener(`error`,()=>{t===this.active&&this.handlers.onError(w(t))})}get playing(){return!this.active.paused&&!this.active.ended}get position(){return this.active.currentTime}get duration(){return Number.isFinite(this.active.duration)?this.active.duration:0}get showingVideo(){return this.active===this.elements.video}ensureGraph(e){let t=globalThis.AudioContext??globalThis.webkitAudioContext;if(t){if(this.context??=new t,this.analyser||(this.analyser=this.context.createAnalyser(),this.analyser.fftSize=S,this.analyser.smoothingTimeConstant=.6,this.analyser.connect(this.context.destination),this.frequencies=new Uint8Array(this.analyser.frequencyBinCount)),!this.wired.has(e))try{this.context.createMediaElementSource(e).connect(this.analyser),this.wired.add(e)}catch{this.wired.add(e)}this.context.resume()}}read(){return this.analyser&&this.analyser.getByteFrequencyData(this.frequencies),this.frequencies}levels(){if(!this.analyser)return[0,0];let e=this.read(),t=0;for(let n of e)t+=n;let n=e.length===0?0:t/e.length/255;return[Math.min(1,n*2.2),Math.min(1,n*2.2)]}async load(e,t){let n=e.objectUrl?e.video?`mp4`:`audio`:d({src:e.url}),r=e.video||!C(n)?this.elements.video:this.elements.audio;r!==this.active&&(this.active.pause(),this.active.removeAttribute(`src`),this.active.load(),this.active=r),this.attached?.destroy(),this.attached=null;try{this.attached=await h(this.active,{src:e.url,kind:n,unplayableAdvice:`VLC or mpv will play it; nixamp can only hand it to your browser.`,onError:e=>this.handlers.onError(e),onNotice:e=>{e&&this.handlers.onError(e)}})}catch(e){this.handlers.onError(e instanceof Error?e.message:`that would not play`);return}t&&await this.play()}async play(){this.ensureGraph(this.active);try{await this.active.play()}catch(e){this.handlers.onError(e instanceof Error?e.message:`playback was refused`)}}pause(){this.active.pause()}stop(){this.active.pause(),this.active.currentTime=0,this.attached?.destroy(),this.attached=null}seek(e){Number.isFinite(e)&&(this.active.currentTime=Math.max(0,e))}set volume(e){this.elements.audio.volume=e,this.elements.video.volume=e}get volume(){return this.active.volume}};function w(e){switch(e.error?.code){case MediaError.MEDIA_ERR_ABORTED:return`playback was aborted`;case MediaError.MEDIA_ERR_NETWORK:return`the network dropped mid-track`;case MediaError.MEDIA_ERR_DECODE:return`this browser could not decode that`;case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:return`this browser cannot play that format`;default:return`playback failed`}}function ne(){return{revision:0,tracks:[],trackCount:0,index:0,playing:!1,position:0,bars:[],levels:[0,0],silent:!0,note:``,root:``}}function re(e,t){return{...t,tracks:t.tracks??e.tracks}}function T(e){let t=e.trim();if(t===``)return``;/^https?:\/\//i.test(t)||(t=`http://${t}`);let n;try{n=new URL(t)}catch{return``}let r=n.pathname.replace(/\/+$/,``);return r=r.replace(/\/api(\/.*)?$/,``),`${n.origin}${r}`}function E(e,t,n=``){let r=`${e===``?``:T(e)}${t.startsWith(`/`)?t:`/${t}`}`;return n?`${r}${r.includes(`?`)?`&`:`?`}k=${encodeURIComponent(n)}`:r}function D(e){let t=e.trim();if(t===``)return{base:``,key:``};let n;try{n=new URL(/^https?:\/\//i.test(t)?t:`http://${t}`)}catch{return{base:``,key:``}}let r=/^\/s\/([^/]+)\/?$/.exec(n.pathname),i=r?.[1]??n.searchParams.get(`k`)??``;return r&&(n.pathname=`/`),n.searchParams.delete(`k`),{base:T(`${n.origin}${n.pathname}`),key:decodeURIComponent(i)}}function O(e,t,n=0,r=``){return E(e,n>0?`/api/media/${t}?kbps=${Math.round(n)}`:`/api/media/${t}`,r)}function k(e){if(typeof e!=`object`||!e)return null;let t=e,n=ne(),r=(e,t)=>typeof e==`number`&&Number.isFinite(e)?e:t,i=Array.isArray(t.levels)?t.levels:[],a=Array.isArray(t.tracks)?t.tracks.map(e=>{let t=typeof e==`object`&&e?e:{};return{title:typeof t.title==`string`?t.title:`Untitled`,artist:typeof t.artist==`string`?t.artist:``,album:typeof t.album==`string`?t.album:``,duration:r(t.duration,0),...t.video===!0?{video:!0}:{}}}):void 0;return{revision:r(t.revision,0),...a?{tracks:a}:{},trackCount:r(t.trackCount,a?.length??0),index:r(t.index,0),playing:t.playing===!0,position:r(t.position,0),bars:Array.isArray(t.bars)?t.bars.map(e=>r(e,0)):[],levels:[r(i[0],0),r(i[1],0)],silent:t.silent===!0,note:typeof t.note==`string`?t.note:``,root:typeof t.root==`string`?t.root:n.root}}var ie=class{handlers;source=null;base=``;key=``;lastRevision=-1;constructor(e){this.handlers=e}get address(){return this.base}url(e){return E(this.base,e,this.key)}get connected(){return this.source!==null}connect(e){let{base:t,key:n}=D(e);this.close(),this.base=t,this.key=n,this.lastRevision=-1,this.handlers.onStatus(`connecting`);let r=new EventSource(E(t,`/api/events`,n));this.source=r,r.onopen=()=>this.handlers.onStatus(`live`),r.onmessage=e=>{let t=k(A(e.data));t&&(t.revision<this.lastRevision||(this.lastRevision=t.revision,this.handlers.onStatus(`live`),this.handlers.onSnapshot(t)))},r.onerror=()=>{this.handlers.onStatus(`error`,`reconnecting…`)}}async send(e){if(this.base===``&&!this.connected)return;let t=await fetch(E(this.base,`/api/command`,this.key),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e)});if(!t.ok){this.handlers.onStatus(`error`,`command refused (${t.status})`);return}let n=k(await t.json());n&&this.handlers.onSnapshot(n)}media(e,t=0){return O(this.base,e,t,this.key)}close(){this.source?.close(),this.source=null}};function A(e){try{return JSON.parse(e)}catch{return null}}async function ae(e,t,n=``){try{let r=await fetch(E(e,`/api/state`,n),{signal:t});return r.ok?k(await r.json()):null}catch{return null}}async function oe(e,t,n=``){try{let r=await fetch(E(e,`/api/health`,n),{signal:t});if(!r.ok)return null;let i=await r.json();return i.name===`nixamp`?i.version??`unknown`:null}catch{return null}}function se(e,t=globalThis.location?.protocol){return t!==`https:`||!/^http:\/\//i.test(e.trim())?``:`This page is https, and a browser refuses every request from an https page to an http one. Open that address directly, or give the server a certificate: nixamp serve --tls-cert cert.pem --tls-key key.pem.`}var j=.14,M=.02;function ce(e,t){let n=[];for(let r=0;r<=e;r++){let i=r/e,a=Math.round(1*(t/1)**i),o=n[n.length-1];n.push(o===void 0?a:Math.max(a,o+1))}return n}function le(e,t){let n=[];for(let r=0;r+1<t.length;r++){let i=Math.min(t[r],e.length),a=Math.min(Math.max(t[r+1],i+1),e.length),o=0,s=0;for(let t=i;t<a;t++)o+=e[t],s++;n.push(s===0?0:o/s/255)}return n}function ue(e,t,n=j){return t.map((t,r)=>{let i=e[r]??0;return t>=i?t:Math.max(t,i-n)})}function N(e,t,n=M){return t.map((t,r)=>Math.max(t,(e[r]??0)-n))}function de(e,t,n,r,i){let{width:a,height:o}=t;if(a<=0||o<=0||n.length===0)return;e.clearRect(0,0,a,o);let s=a/n.length,c=Math.max(1,s*.72),l=Math.max(2,o*.012);e.fillStyle=i.bar,n.forEach((t,n)=>{let r=Math.max(1,t*(o-l*2));e.fillRect(n*s+(s-c)/2,o-r,c,r)}),e.fillStyle=i.peak,r.forEach((t,n)=>{let r=o-Math.max(1,t*(o-l*2))-l*2;e.fillRect(n*s+(s-c)/2,Math.max(0,r),c,l)})}var P=`nixamp.remote`,F=`nixamp.volume`;function I(e){let t=document.getElementById(e);if(!t)throw Error(`nixamp: #${e} is missing from the shell`);return t}function L(){let n={status:I(`status`),source:I(`source`),install:I(`install`),video:I(`video`),audio:I(`audio`),title:I(`title-line`),album:I(`album-line`),elapsed:I(`elapsed`),total:I(`total`),seek:I(`seek`),canvas:I(`spectrum`),glyphs:I(`glyphs`),levels:I(`levels`),playlist:I(`playlist`),playlistTitle:I(`playlist-panel`),note:I(`note`),files:I(`files`),folder:I(`folder`),remoteUrl:I(`remote-url`),remoteForm:I(`remote-form`),remoteState:I(`remote-state`),disconnect:I(`disconnect`),browse:I(`browse`),accountForm:I(`account-form`),accountEmail:I(`account-email`),accountPassword:I(`account-password`),accountSubmit:I(`account-submit`),accountToggle:I(`account-toggle`),accountProviders:I(`account-providers`),accountPanel:I(`account-panel`),accountElsewhere:I(`account-elsewhere`),accountSignOut:I(`account-signout`),accountNote:I(`account-note`),adminPanel:I(`admin-panel`),adminNote:I(`admin-note`),adminConnections:I(`admin-connections`),adminRestream:I(`admin-restream`),adminSource:I(`admin-source`),directory:I(`directory`),recentNote:I(`recent-note`),recentList:I(`recent-list`),followingNote:I(`following-note`),followingList:I(`following-list`),serversPanel:I(`servers-panel`),serversNote:I(`servers-note`),serversList:I(`servers-list`),notifyPanel:I(`notify-panel`),notifyNote:I(`notify-note`),notifyWeb:I(`notify-web`),notifyEmail:I(`notify-email`),notifySms:I(`notify-sms`),notifyPhone:I(`notify-phone`),notifyPhoneForm:I(`notify-phone-form`),notifyPhoneNote:I(`notify-phone-note`),directoryNote:I(`directory-note`),directoryList:I(`directory-list`),listenHere:I(`listen-here`),volume:I(`volume`),prev:I(`prev`),playPause:I(`play-pause`),stop:I(`stop`),next:I(`next`)},r=`local`,i=[],a=0,o=ne(),s=`idle`,c=``,l=`Pick files, or connect to a nixamp running somewhere else.`,u=!1,d=Array(24).fill(0),f=Array(24).fill(0),p=[],m=()=>r===`remote`&&!n.listenHere.checked,h=new te({audio:n.audio,video:n.video},{onTime:(e,t)=>{let n=i[a];r===`local`&&n&&t>0&&n.duration!==t&&(n.duration=t),M()},onEnded:()=>k(1),onState:()=>M(),onError:e=>{l=e,M()}}),g=new ie({onSnapshot:e=>{o=re(o,e),m()&&(d=e.bars.length>0?e.bars:d,f=N(f,d)),M()},onStatus:(e,t)=>{s=e,c=t??``,M()}}),_=()=>r===`remote`?o.tracks.length:i.length,v=()=>r===`remote`?o.index:a,b=()=>{if(r===`remote`){let e=o.tracks[o.index];return e?t(e):`Nothing loaded.`}let e=i[a];return e?t(e):`Nothing loaded.`},x=()=>(r===`remote`?o.tracks[o.index]:i[a])?.album||`—`,S=()=>m()?o.tracks[o.index]?.duration??0:h.duration,C=()=>m()?o.position:h.position,w=()=>m()?o.playing:h.playing;async function E(e){if(r===`remote`){if(m()){await g.send({type:`play`,index:e});return}await D(e);return}let t=i[e];t&&(a=e,await h.load(t,!0),z(t.video),B(),M())}async function D(e){let t=o.tracks[e];t&&(await h.load({title:t.title,artist:t.artist,album:t.album,duration:t.duration,url:g.media(e,0),video:t.video===!0,objectUrl:!1},!0),z(t.video===!0),B())}async function O(){if(m()){await g.send({type:`toggle`});return}_()!==0&&(h.playing?h.pause():h.position>0?await h.play():await E(v()),M())}async function k(e){let t=_();if(t!==0){if(m()){await g.send({type:e>0?`next`:`prev`});return}await E((v()+e+t)%t)}}async function A(){if(m()){await g.send({type:`stop`});return}h.stop(),d=Array(24).fill(0),f=[...d],M()}let j=e=>`▁▂▃▄▅▆▇█`[Math.max(0,Math.min(7,Math.round(e*7)))];function M(){let t=_(),a=w();n.status.textContent=a?`▶ PLAYING`:`■ STOPPED`,n.status.dataset.playing=String(a),n.title.textContent=b(),n.album.textContent=x();let f=C(),p=S();n.elapsed.textContent=e(f),n.total.textContent=p>0?e(p):`--:--`,u||(n.seek.value=String(p>0?Math.round(f/p*1e3):0),n.seek.disabled=p<=0||m()),n.playPause.textContent=a?`❚❚`:`▶`,n.playPause.setAttribute(`aria-label`,a?`Pause`:`Play`),n.playlistTitle.dataset.title=`Playlist (${t})`,n.source.textContent=r===`remote`?`remote · ${g.address.replace(/^https?:\/\//,``)||`—`}`:i.length>0?`local · ${i.length} files`:`no source`,n.remoteState.textContent=r===`remote`?`${s}${c?` — ${c}`:``}`:`not connected`,n.remoteState.dataset.status=r===`remote`?s:`idle`,n.disconnect.hidden=r!==`remote`;let v=r===`remote`&&o.note!==``?o.note:l;n.note.textContent=v,n.note.hidden=v===``,fe(),n.glyphs.textContent=d.map(j).join(``);let[y,ee]=m()?o.levels:h.levels();n.levels.textContent=`L${`▮`.repeat(Math.round(y*6)).padEnd(6,`·`)} R${`▮`.repeat(Math.round(ee*6)).padEnd(6,`·`)}`}let L=``;function fe(){let a=r===`remote`?o.tracks.map(e=>[t(e),e.duration]):i.map(e=>[t(e),e.duration]),s=`${r}:${a.map(([e,t])=>`${e}@${t}`).join(`|`)}`;s!==L&&(L=s,n.playlist.replaceChildren(...a.map(([t,n],r)=>{let i=document.createElement(`li`);i.className=`row`,i.dataset.index=String(r);let a=document.createElement(`span`);a.className=`n`,a.textContent=String(r+1).padStart(2,` `);let o=document.createElement(`span`);o.className=`name`,o.textContent=t;let s=document.createElement(`span`);return s.className=`time`,s.textContent=n>0?e(n):`--:--`,i.append(a,o,s),i})));let c=v(),l=w();Array.from(n.playlist.children).forEach((e,t)=>{let n=e;n.classList.toggle(`selected`,t===c),n.classList.toggle(`playing`,t===c&&l)}),n.playlist.children[c]?.scrollIntoView({block:`nearest`})}function R(){let t=n.canvas,r=Math.min(2,globalThis.devicePixelRatio||1),i=Math.round(t.clientWidth*r),a=Math.round(t.clientHeight*r);i>0&&a>0&&(t.width!==i||t.height!==a)&&(t.width=i,t.height=a);let s=t.getContext(`2d`);if(m())f=N(f,d);else{let e=h.read();e.length>0&&(p.length!==25&&(p=ce(24,e.length)),d=ue(d,le(e,p)),f=N(f,d))}if(s){let e=getComputedStyle(document.documentElement);de(s,{width:t.width,height:t.height},d,f,{bar:e.getPropertyValue(`--green`).trim()||`#4af689`,peak:e.getPropertyValue(`--green-dim`).trim()||`#227a4a`,background:`transparent`})}if(w()){n.glyphs.textContent=d.map(j).join(``);let[t,r]=m()?o.levels:h.levels();n.levels.textContent=`L${`▮`.repeat(Math.round(t*6)).padEnd(6,`·`)} R${`▮`.repeat(Math.round(r*6)).padEnd(6,`·`)}`,n.elapsed.textContent=e(C());let i=S();!u&&i>0&&(n.seek.value=String(Math.round(C()/i*1e3)))}requestAnimationFrame(R)}function z(e){n.video.hidden=!e}function B(){`mediaSession`in navigator&&(navigator.mediaSession.metadata=new MediaMetadata({title:b(),album:x(),artist:`nixamp`,artwork:[{src:`/icons/icon-512.png`,sizes:`512x512`,type:`image/png`}]}),navigator.mediaSession.setActionHandler(`play`,()=>void O()),navigator.mediaSession.setActionHandler(`pause`,()=>void O()),navigator.mediaSession.setActionHandler(`nexttrack`,()=>void k(1)),navigator.mediaSession.setActionHandler(`previoustrack`,()=>void k(-1)))}n.playlist.addEventListener(`click`,e=>{let t=e.target.closest(`li`),n=Number(t?.dataset.index);Number.isInteger(n)&&E(n)}),n.prev.addEventListener(`click`,()=>void k(-1)),n.next.addEventListener(`click`,()=>void k(1)),n.stop.addEventListener(`click`,()=>void A()),n.playPause.addEventListener(`click`,()=>void O()),n.seek.addEventListener(`input`,()=>{u=!0}),n.seek.addEventListener(`change`,()=>{let e=S();e>0&&h.seek(Number(n.seek.value)/1e3*e),u=!1}),n.volume.addEventListener(`input`,()=>{let e=Number(n.volume.value)/100;h.volume=e;try{localStorage.setItem(F,String(e))}catch{}});let V=e=>{e.addEventListener(`change`,()=>{let t=y(Array.from(e.files??[]));if(t.length===0){l=`Nothing playable in that selection.`,M();return}ee(i),i=t,a=0,r=`local`,g.close(),l=``,E(0)})};V(n.files),V(n.folder),n.remoteForm.addEventListener(`submit`,e=>{e.preventDefault();let t=T(n.remoteUrl.value);if(t===``){l=`That is not an address.`,M();return}(async()=>{s=`connecting`,M();let e=se(t);if(e){s=`error`,c=e,l=e,r=`local`,M();return}if(await oe(t)===null){s=`error`,c=`no nixamp answered there`,r=`local`,M();return}r=`remote`,l=``;try{localStorage.setItem(P,t)}catch{}g.connect(t),M()})()});let H=async()=>{n.directory.hidden=!1,n.directoryNote.textContent=`Looking for live streams…`,n.directoryList.replaceChildren();let e;try{let t=await fetch(`/api/directory`);if(!t.ok)throw Error(String(t.status));let n=await t.json();e=n.streams??[],me(n.recent??[])}catch{n.directoryNote.textContent=`The directory is not answering. Type an address instead.`;return}if(e.length===0){n.directoryNote.textContent=`Nobody is streaming right now.`;return}n.directoryNote.textContent=`${e.length} live ${e.length===1?`stream`:`streams`}:`;for(let t of e){let e=document.createElement(`li`),r=document.createElement(`button`);r.type=`button`;let i=document.createElement(`span`);i.className=`name`,i.textContent=t.name;let a=document.createElement(`span`);a.className=`detail`;let o=[t.nowPlaying,`${t.tracks} tracks`].filter(Boolean);t.code&&o.push(t.callers?`☎ ${t.code} · ${t.callers} on the phone`:`☎ ${t.code}`),a.textContent=o.join(` · `),r.append(i,a),r.addEventListener(`click`,()=>{n.remoteUrl.value=t.url,n.directory.hidden=!0,n.remoteForm.requestSubmit()}),e.append(r),t.ownerId&&Z&&t.ownerId!==Z&&e.append(he(t.ownerId,t.name)),n.directoryList.append(e)}};if(location.pathname.replace(/\/+$/,``)===`/directory`){document.body.classList.add(`route-directory`);let e=document.getElementById(`directory-back`);e&&(e.hidden=!1),H()}let U=null,pe=e=>{n.adminConnections.replaceChildren();let t=document.createElement(`tr`);for(let e of[`Where`,`Network`,`Kind`,`Client`,`Track`,`Sent`]){let n=document.createElement(`th`);n.textContent=e,t.append(n)}n.adminConnections.append(t);for(let t of e.slice(0,40)){let e=document.createElement(`tr`);t.endedAt!==null&&(e.className=`ended`);let r=[[t.address,``],[t.network,`network-${t.network}`],[t.kind,``],[t.agent,``],[t.track||`—`,``],[`${Math.round(t.bytes/1024)} KiB`,``]];for(let[t,n]of r){let r=document.createElement(`td`);r.textContent=t,n&&(r.className=n),e.append(r)}n.adminConnections.append(e)}},W=async()=>{try{let e=await fetch(`/api/connections`);if(!e.ok)return;let t=await e.json();n.adminNote.textContent=`${t.active??0} listening now.`,pe(t.connections??[])}catch{n.adminNote.textContent=`lost touch with the server`}},G=async()=>{let e=!1,t=null;try{let n=await fetch(g.url(`/api/admin`));if(n.ok){let r=await n.json();e=r.allowed===!0,t=r.as??null}}catch{e=!1}n.adminPanel.hidden=!e,U&&clearInterval(U),U=null,e&&(n.adminNote.textContent=t===`owner`?`You own this server.`:`You hold this server's control link.`,W(),U=setInterval(()=>void W(),2e3))};n.adminRestream.addEventListener(`submit`,e=>{e.preventDefault();let t=n.adminSource.value.trim();t&&(async()=>{try{let e=await fetch(g.url(`/api/source`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:t})}),r=await e.json();n.adminNote.textContent=e.ok?`Now serving ${t}.`:r.error??`that did not work`,e.ok&&(n.adminSource.value=``)}catch{n.adminNote.textContent=`could not reach the server`}})()});let K=e=>{let t=Math.max(1,Math.round((Date.now()-e)/6e4));if(t<60)return`${t} minute${t===1?``:`s`} ago`;let n=Math.round(t/60);return`${n} hour${n===1?``:`s`} ago`},me=e=>{n.recentList.replaceChildren();let t=Z?e.filter(e=>e.ownerId&&e.ownerId!==Z):[];if(n.recentNote.hidden=t.length===0,t.length!==0)for(let e of t){let t=document.createElement(`li`),r=document.createElement(`span`);r.className=`recent-label`;let i=document.createElement(`span`);i.className=`name`,i.textContent=e.name;let a=document.createElement(`span`);a.className=`detail`,a.textContent=e.nowPlaying?`${e.nowPlaying} · ended ${K(e.endedAt)}`:`ended ${K(e.endedAt)}`,r.append(i,a),t.append(r,he(e.ownerId,e.name)),n.recentList.append(t)}},q=async()=>{n.serversList.replaceChildren();try{let e=await fetch(`/api/v1/servers`);if(!e.ok){n.serversPanel.hidden=!0;return}let t=(await e.json()).servers??[];n.serversPanel.hidden=!1,n.serversNote.textContent=t.length===0?"No servers yet. `nixamp server add --here` remembers the one you are running.":`The machines on your account. Open one, or forget it.`;for(let e of t){let t=document.createElement(`li`),r=document.createElement(`span`);r.className=`recent-label`;let i=document.createElement(`span`);i.className=`name`,i.textContent=e.name;let a=document.createElement(`span`);a.className=`detail`,a.textContent=e.url,r.append(i,a);let o=document.createElement(`a`);o.className=`button`,o.textContent=`Open`,o.href=e.key?`${e.url}/s/${e.key}`:e.url,o.rel=`noreferrer`;let s=document.createElement(`button`);s.type=`button`,s.className=`ghost`,s.textContent=`Forget`,s.addEventListener(`click`,()=>{(async()=>{s.disabled=!0;try{await fetch(`/api/v1/servers/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await q()}catch{s.disabled=!1}})()}),t.append(r,o,s),n.serversList.append(t)}}catch{n.serversPanel.hidden=!0}},J=async()=>{n.followingList.replaceChildren();try{let e=await fetch(`/api/v1/follows`);if(!e.ok){n.followingNote.hidden=!0;return}let t=(await e.json()).following??[];n.followingNote.hidden=t.length===0;for(let e of t){let t=document.createElement(`li`),r=document.createElement(`span`);r.className=`recent-label`;let i=document.createElement(`span`);i.className=`name`,i.textContent=e.name||`a nixamp`;let a=document.createElement(`span`);a.className=`detail`,a.textContent=e.live?`live now`:`not streaming`,r.append(i,a);let o=document.createElement(`button`);o.type=`button`,o.className=`ghost follow`,o.textContent=`Unfollow`,o.addEventListener(`click`,()=>{(async()=>{o.disabled=!0;try{await fetch(`/api/v1/follows/${encodeURIComponent(e.id)}`,{method:`DELETE`}),t.remove(),n.followingList.children.length===0&&(n.followingNote.hidden=!0)}finally{o.disabled=!1}})()}),t.append(r,o),n.followingList.append(t)}}catch{n.followingNote.hidden=!0}},he=(e,t)=>{let n=document.createElement(`button`);n.type=`button`,n.className=`ghost follow`,n.textContent=`Follow`,n.setAttribute(`aria-label`,`Follow ${t}`);let r=e=>{n.textContent=e?`Following`:`Follow`,n.dataset.following=e?`yes`:`no`};return(async()=>{try{let t=await fetch(`/api/v1/follows/${encodeURIComponent(e)}`);t.ok&&r((await t.json()).following===!0)}catch{}})(),n.addEventListener(`click`,()=>{(async()=>{let t=n.dataset.following===`yes`;n.disabled=!0;try{(await fetch(`/api/v1/follows/${encodeURIComponent(e)}`,{method:t?`DELETE`:`PUT`,headers:{"content-type":`application/json`},body:t?void 0:`{}`})).ok&&(r(!t),J())}catch{}finally{n.disabled=!1}})()}),n},ge=e=>{let t=(e+`=`.repeat((4-e.length%4)%4)).replace(/-/g,`+`).replace(/_/g,`/`),n=atob(t),r=new Uint8Array(new ArrayBuffer(n.length));for(let e=0;e<n.length;e+=1)r[e]=n.charCodeAt(e);return r},_e=()=>`serviceWorker`in navigator&&`PushManager`in window&&`Notification`in window,ve=async()=>{if(!_e())return n.notifyNote.textContent=`This browser cannot show notifications.`,!1;if(Notification.permission===`denied`)return n.notifyNote.textContent=`This browser is blocking notifications. Allow them in site settings first.`,!1;if(await Notification.requestPermission()!==`granted`)return n.notifyNote.textContent=`Not allowed, so nothing will be sent here.`,!1;try{let e=await navigator.serviceWorker.ready,{publicKey:t}=await(await fetch(`/api/v1/notify/key`)).json();if(!t)return n.notifyNote.textContent=`This server is not set up to send notifications.`,!1;let r=await e.pushManager.getSubscription()??await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:ge(t)}),i=await fetch(`/api/v1/notify/subscribe`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(r.toJSON())});if(!i.ok)throw Error(String(i.status));return n.notifyNote.textContent=`This device will be told.`,!0}catch{return n.notifyNote.textContent=`Could not set this device up.`,!1}},ye=async()=>{try{let e=await(await navigator.serviceWorker.ready).pushManager.getSubscription();if(!e)return;await fetch(`/api/v1/notify/subscribe?endpoint=${encodeURIComponent(e.endpoint)}`,{method:`DELETE`}),await e.unsubscribe()}catch{}},Y=async e=>{try{let t=await fetch(`/api/v1/notify/prefs`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify(e)}),r=await t.json();n.notifyPhoneNote.textContent=t.ok?``:r.error??`that did not save`,t.ok&&typeof r.phone==`string`&&(n.notifyPhone.value=r.phone)}catch{n.notifyPhoneNote.textContent=`could not reach nixamp.com`}},be=async()=>{try{let e=await fetch(`/api/v1/notify/prefs`);if(!e.ok)return;let t=await e.json();n.notifyEmail.checked=t.wantsEmail!==!1,n.notifySms.checked=t.wantsSms===!0,n.notifyPhone.value=t.phone??``;let r=_e()&&Notification.permission===`granted`?await(await navigator.serviceWorker.ready).pushManager.getSubscription()!==null:!1;n.notifyWeb.checked=t.wantsWeb!==!1&&r,n.notifyNote.textContent=r?`Get told when someone you follow goes live.`:`Turn on “On this device” to be told here.`}catch{}};n.notifyWeb.addEventListener(`change`,()=>{(async()=>{if(n.notifyWeb.checked){let e=await ve();n.notifyWeb.checked=e,await Y({wantsWeb:e});return}await ye(),await Y({wantsWeb:!1}),n.notifyNote.textContent=`Turn on “On this device” to be told here.`})()}),n.notifyEmail.addEventListener(`change`,()=>{Y({wantsEmail:n.notifyEmail.checked})}),n.notifySms.addEventListener(`change`,()=>{(async()=>{if(n.notifySms.checked&&!n.notifyPhone.value.trim()){n.notifyPhoneNote.textContent=`Add a phone number first.`,n.notifySms.checked=!1,n.notifyPhone.focus();return}await Y({wantsSms:n.notifySms.checked})})()}),n.notifyPhoneForm.addEventListener(`submit`,e=>{e.preventDefault(),Y({phone:n.notifyPhone.value.trim()})});let X=!1,Z=``,Q=e=>{let t=e!==null;n.notifyPanel.hidden=!t,t?(be(),J(),q()):(n.serversPanel.hidden=!0,n.followingNote.hidden=!0,n.followingList.replaceChildren(),n.recentNote.hidden=!0,n.recentList.replaceChildren()),n.accountForm.hidden=t,n.accountProviders.hidden=t||n.accountProviders.childElementCount===0,n.accountSignOut.hidden=!t,n.accountNote.textContent=t?`Signed in as ${e}.`:X?`Create an account on nixamp.com.`:`Sign in to nixamp.com to publish and get paid.`,n.accountSubmit.textContent=X?`Create account`:`Sign in`,n.accountToggle.textContent=X?`I have one`:`Create one`,n.accountPassword.autocomplete=X?`new-password`:`current-password`};n.accountToggle.addEventListener(`click`,()=>{X=!X,Q(null)}),n.accountForm.addEventListener(`submit`,e=>{e.preventDefault();let t=n.accountEmail.value.trim(),r=n.accountPassword.value;(async()=>{n.accountSubmit.disabled=!0;try{let e=await fetch(`/api/v1/auth/${X?`signup`:`login`}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({email:t,password:r})}),i=await e.json();if(!e.ok){n.accountNote.textContent=i.error??`that did not work`;return}Z=i.account?.id??``,n.accountPassword.value=``,Q(i.account?.email??t),G()}catch{n.accountNote.textContent=`could not reach nixamp.com`}finally{n.accountSubmit.disabled=!1}})()}),n.accountSignOut.addEventListener(`click`,()=>{(async()=>{try{await fetch(`/api/v1/auth/logout`,{method:`POST`})}catch{}Z=``,Q(null),G()})()}),(async()=>{let e=[],t=!1;try{let n=await fetch(`/api/v1/auth/providers`);n.ok&&(t=!0,e=(await n.json()).providers??[])}catch{}n.accountProviders.replaceChildren(),n.accountProviders.hidden=e.length===0,n.accountPanel.hidden=!t,n.accountElsewhere.hidden=t;for(let t of e){let e=document.createElement(`a`);e.className=`button`,e.href=`/api/v1/${encodeURIComponent(t.id)}/oauth/start`,e.textContent=`Continue with ${t.name}`,n.accountProviders.append(e)}})(),(async()=>{try{let e=await fetch(`/api/v1/auth/me`),t=await e.json();Z=e.ok?t.account?.id??``:``,Q(e.ok?t.account?.email??`you`:null)}catch{Z=``,Q(null)}})(),G(),n.browse.addEventListener(`click`,()=>{if(!n.directory.hidden){n.directory.hidden=!0;return}H(),n.directory.scrollIntoView({behavior:`smooth`,block:`nearest`})}),n.disconnect.addEventListener(`click`,()=>{g.close(),r=`local`,s=`idle`,c=``,M()}),n.listenHere.addEventListener(`change`,()=>{r===`remote`&&(async()=>{n.listenHere.checked?(await g.send({type:`stop`}),await D(o.index)):h.stop(),M()})()}),document.addEventListener(`keydown`,e=>{let t=e.target;if(!(t&&/^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)))switch(e.key){case` `:e.preventDefault(),O();return;case`s`:A();return;case`n`:case`ArrowRight`:k(1);return;case`p`:case`ArrowLeft`:k(-1);return;case`ArrowDown`:e.preventDefault(),E(Math.min(_()-1,v()+1));return;case`ArrowUp`:e.preventDefault(),E(Math.max(0,v()-1));return}});let $=null;globalThis.addEventListener(`beforeinstallprompt`,e=>{e.preventDefault(),$=e,n.install.hidden=!1}),n.install.addEventListener(`click`,()=>{$?.prompt(),$=null,n.install.hidden=!0});try{let e=localStorage.getItem(F);e!==null&&(n.volume.value=String(Math.round(Number(e)*100)),h.volume=Number(e));let t=localStorage.getItem(P);t&&(n.remoteUrl.value=t)}catch{}(async()=>{if(n.remoteUrl.value!==``)return;let e=globalThis.location.origin;if(await oe(e)===null)return;let t=await ae(e);t&&t.trackCount!==0&&(n.remoteUrl.value=e,r=`remote`,l=``,g.connect(e),M())})(),M(),requestAnimationFrame(R)}L(),`serviceWorker`in navigator&&globalThis.addEventListener(`load`,()=>{navigator.serviceWorker.register(`/sw.js`).catch(()=>{})});export{c as t};
|