sello 0.1.4 → 0.1.6
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/cli/sello.js +95 -13
- package/package.json +1 -1
- package/src/cli/sello.ts +95 -13
package/dist/cli/sello.js
CHANGED
|
@@ -44,6 +44,25 @@ import { createSelloService } from "../sdk/service.js";
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
47
66
|
|
|
48
67
|
|
|
49
68
|
const textEncoder = new TextEncoder();
|
|
@@ -285,16 +304,17 @@ async function handleDevRequest(input
|
|
|
285
304
|
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
|
|
286
305
|
|
|
287
306
|
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
|
|
288
|
-
const result =
|
|
289
|
-
authorizationTokenBytes: textEncoder.encode(state.agentToken),
|
|
290
|
-
trustedLogs: [log],
|
|
291
|
-
registry,
|
|
292
|
-
ownerPrivateKey: normalizeHpkePrivateKey(state.ownerKey),
|
|
293
|
-
});
|
|
307
|
+
const result = verifyDevActions({ log, state, registry });
|
|
294
308
|
sendHtml(response, renderActionsHtml(result));
|
|
295
309
|
return;
|
|
296
310
|
}
|
|
297
311
|
|
|
312
|
+
if (request.method === "GET" && url.pathname === "/api/actions") {
|
|
313
|
+
const result = verifyDevActions({ log, state, registry });
|
|
314
|
+
sendJson(response, 200, actionsViewModel(result));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
298
318
|
if (request.method === "POST" && url.pathname === "/api/entries") {
|
|
299
319
|
const body = await readJson(request);
|
|
300
320
|
if (!isRecord(body) || typeof body.envelope !== "string") {
|
|
@@ -326,6 +346,19 @@ async function handleDevRequest(input
|
|
|
326
346
|
sendJson(response, 404, { error: "not found" });
|
|
327
347
|
}
|
|
328
348
|
|
|
349
|
+
function verifyDevActions(input
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
) {
|
|
354
|
+
return verifyReceipts({
|
|
355
|
+
authorizationTokenBytes: textEncoder.encode(input.state.agentToken),
|
|
356
|
+
trustedLogs: [input.log],
|
|
357
|
+
registry: input.registry,
|
|
358
|
+
ownerPrivateKey: normalizeHpkePrivateKey(input.state.ownerKey),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
329
362
|
async function loadViewerRegistry(devState ) {
|
|
330
363
|
if (process.env.SELLO_REGISTRY_PATH) {
|
|
331
364
|
return parseRegistry(readFileBytes(process.env.SELLO_REGISTRY_PATH));
|
|
@@ -461,16 +494,40 @@ function printActions(result ) {
|
|
|
461
494
|
}
|
|
462
495
|
}
|
|
463
496
|
|
|
497
|
+
function actionsViewModel(result ) {
|
|
498
|
+
return {
|
|
499
|
+
receipts: result.receipts.map((record) => ({
|
|
500
|
+
integratedTime: record.integratedTime,
|
|
501
|
+
serviceIdentifier: record.serviceIdentifier,
|
|
502
|
+
actionType: record.receipt["action-type"],
|
|
503
|
+
resultStatus: record.receipt["result-status"],
|
|
504
|
+
status: record.status,
|
|
505
|
+
logUrl: record.logUrl,
|
|
506
|
+
logCompleteness: record.logCompleteness,
|
|
507
|
+
sameSecondActivity: record.sameSecondActivity,
|
|
508
|
+
})),
|
|
509
|
+
rejected: result.rejected.map((record) => ({
|
|
510
|
+
code: record.code,
|
|
511
|
+
message: record.message,
|
|
512
|
+
...(record.logUrl === undefined ? {} : { logUrl: record.logUrl }),
|
|
513
|
+
...(record.integratedTime === undefined ? {} : { integratedTime: record.integratedTime }),
|
|
514
|
+
})),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
464
518
|
function renderActionsHtml(result ) {
|
|
465
|
-
const
|
|
519
|
+
const view = actionsViewModel(result);
|
|
520
|
+
const actionCount = view.receipts.length;
|
|
521
|
+
const rejectedCount = view.rejected.length;
|
|
522
|
+
const rows = view.receipts.map((record) => `
|
|
466
523
|
<tr>
|
|
467
524
|
<td>${escapeHtml(record.integratedTime)}</td>
|
|
468
525
|
<td>${escapeHtml(record.serviceIdentifier)}</td>
|
|
469
|
-
<td>${escapeHtml(record.
|
|
470
|
-
<td>${escapeHtml(record.
|
|
526
|
+
<td>${escapeHtml(record.actionType)}</td>
|
|
527
|
+
<td>${escapeHtml(record.resultStatus)}</td>
|
|
471
528
|
<td>${escapeHtml(record.status)}</td>
|
|
472
529
|
</tr>`).join("");
|
|
473
|
-
const rejected =
|
|
530
|
+
const rejected = view.rejected.map((record) => `
|
|
474
531
|
<li><strong>${escapeHtml(record.code)}</strong>: ${escapeHtml(record.message)}</li>`).join("");
|
|
475
532
|
|
|
476
533
|
return `<!doctype html>
|
|
@@ -478,23 +535,48 @@ function renderActionsHtml(result ) {
|
|
|
478
535
|
<head>
|
|
479
536
|
<meta charset="utf-8">
|
|
480
537
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
538
|
+
<meta http-equiv="refresh" content="2">
|
|
481
539
|
<title>Sello Actions</title>
|
|
482
540
|
<style>
|
|
483
541
|
body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 40px; color: #17201d; }
|
|
542
|
+
header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
|
|
543
|
+
h1 { margin: 0; font-size: 40px; line-height: 1.1; }
|
|
544
|
+
h2 { margin: 32px 0 8px; font-size: 20px; }
|
|
545
|
+
code { background: #eef3f0; border-radius: 4px; padding: 2px 6px; }
|
|
484
546
|
table { border-collapse: collapse; width: 100%; margin-top: 16px; }
|
|
485
547
|
th, td { border-bottom: 1px solid #d8dfdc; padding: 10px 8px; text-align: left; }
|
|
486
548
|
th { color: #52615b; font-weight: 600; }
|
|
549
|
+
.summary { color: #52615b; font-size: 15px; margin: 0; white-space: nowrap; }
|
|
487
550
|
.empty { color: #52615b; margin-top: 16px; }
|
|
551
|
+
.empty p { margin: 8px 0; }
|
|
552
|
+
@media (max-width: 720px) {
|
|
553
|
+
body { margin: 24px; }
|
|
554
|
+
header { display: block; }
|
|
555
|
+
h1 { font-size: 32px; }
|
|
556
|
+
.summary { margin-top: 8px; white-space: normal; }
|
|
557
|
+
table { display: block; overflow-x: auto; }
|
|
558
|
+
}
|
|
488
559
|
</style>
|
|
489
560
|
</head>
|
|
490
561
|
<body>
|
|
491
|
-
<
|
|
492
|
-
|
|
493
|
-
|
|
562
|
+
<header>
|
|
563
|
+
<h1>Sello Actions</h1>
|
|
564
|
+
<p class="summary">${escapeHtml(actionCountLabel(actionCount))}</p>
|
|
565
|
+
</header>
|
|
566
|
+
${rows ? `<table><thead><tr><th>Integrated time</th><th>Service</th><th>Action</th><th>Result</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>` : `<section class="empty"><p>No verified actions yet.</p><p>In another terminal, run <code>npx sello emit-demo</code>.</p></section>`}
|
|
567
|
+
${rejected ? `<h2>Rejected receipts</h2><p class="summary">${escapeHtml(rejectedCountLabel(rejectedCount))}</p><ul>${rejected}</ul>` : ""}
|
|
494
568
|
</body>
|
|
495
569
|
</html>`;
|
|
496
570
|
}
|
|
497
571
|
|
|
572
|
+
function actionCountLabel(count ) {
|
|
573
|
+
return count === 1 ? "1 verified action" : `${count} verified actions`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function rejectedCountLabel(count ) {
|
|
577
|
+
return count === 1 ? "1 rejected receipt" : `${count} rejected receipts`;
|
|
578
|
+
}
|
|
579
|
+
|
|
498
580
|
function verifyHttpProof(entry ) {
|
|
499
581
|
if (!isRecord(entry.proof)) {
|
|
500
582
|
return false;
|
package/package.json
CHANGED
package/src/cli/sello.ts
CHANGED
|
@@ -47,6 +47,25 @@ type DevState = {
|
|
|
47
47
|
registryJson: string;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
type ActionsViewModel = {
|
|
51
|
+
receipts: {
|
|
52
|
+
integratedTime: string;
|
|
53
|
+
serviceIdentifier: string;
|
|
54
|
+
actionType: string;
|
|
55
|
+
resultStatus: string;
|
|
56
|
+
status: string;
|
|
57
|
+
logUrl: string;
|
|
58
|
+
logCompleteness: string;
|
|
59
|
+
sameSecondActivity: boolean;
|
|
60
|
+
}[];
|
|
61
|
+
rejected: {
|
|
62
|
+
code: string;
|
|
63
|
+
message: string;
|
|
64
|
+
logUrl?: string;
|
|
65
|
+
integratedTime?: string;
|
|
66
|
+
}[];
|
|
67
|
+
};
|
|
68
|
+
|
|
50
69
|
const textEncoder = new TextEncoder();
|
|
51
70
|
const textDecoder = new TextDecoder();
|
|
52
71
|
const command = process.argv[2] ?? "help";
|
|
@@ -286,16 +305,17 @@ async function handleDevRequest(input: {
|
|
|
286
305
|
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
|
|
287
306
|
|
|
288
307
|
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
|
|
289
|
-
const result =
|
|
290
|
-
authorizationTokenBytes: textEncoder.encode(state.agentToken),
|
|
291
|
-
trustedLogs: [log],
|
|
292
|
-
registry,
|
|
293
|
-
ownerPrivateKey: normalizeHpkePrivateKey(state.ownerKey),
|
|
294
|
-
});
|
|
308
|
+
const result = verifyDevActions({ log, state, registry });
|
|
295
309
|
sendHtml(response, renderActionsHtml(result));
|
|
296
310
|
return;
|
|
297
311
|
}
|
|
298
312
|
|
|
313
|
+
if (request.method === "GET" && url.pathname === "/api/actions") {
|
|
314
|
+
const result = verifyDevActions({ log, state, registry });
|
|
315
|
+
sendJson(response, 200, actionsViewModel(result));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
299
319
|
if (request.method === "POST" && url.pathname === "/api/entries") {
|
|
300
320
|
const body = await readJson(request);
|
|
301
321
|
if (!isRecord(body) || typeof body.envelope !== "string") {
|
|
@@ -327,6 +347,19 @@ async function handleDevRequest(input: {
|
|
|
327
347
|
sendJson(response, 404, { error: "not found" });
|
|
328
348
|
}
|
|
329
349
|
|
|
350
|
+
function verifyDevActions(input: {
|
|
351
|
+
log: MockTransparencyLog;
|
|
352
|
+
state: DevState;
|
|
353
|
+
registry: ReturnType<typeof parseRegistry>;
|
|
354
|
+
}): ReturnType<typeof verifyReceipts> {
|
|
355
|
+
return verifyReceipts({
|
|
356
|
+
authorizationTokenBytes: textEncoder.encode(input.state.agentToken),
|
|
357
|
+
trustedLogs: [input.log],
|
|
358
|
+
registry: input.registry,
|
|
359
|
+
ownerPrivateKey: normalizeHpkePrivateKey(input.state.ownerKey),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
330
363
|
async function loadViewerRegistry(devState: DevState | undefined) {
|
|
331
364
|
if (process.env.SELLO_REGISTRY_PATH) {
|
|
332
365
|
return parseRegistry(readFileBytes(process.env.SELLO_REGISTRY_PATH));
|
|
@@ -462,16 +495,40 @@ function printActions(result: ReturnType<typeof verifyReceipts>): void {
|
|
|
462
495
|
}
|
|
463
496
|
}
|
|
464
497
|
|
|
498
|
+
function actionsViewModel(result: ReturnType<typeof verifyReceipts>): ActionsViewModel {
|
|
499
|
+
return {
|
|
500
|
+
receipts: result.receipts.map((record) => ({
|
|
501
|
+
integratedTime: record.integratedTime,
|
|
502
|
+
serviceIdentifier: record.serviceIdentifier,
|
|
503
|
+
actionType: record.receipt["action-type"],
|
|
504
|
+
resultStatus: record.receipt["result-status"],
|
|
505
|
+
status: record.status,
|
|
506
|
+
logUrl: record.logUrl,
|
|
507
|
+
logCompleteness: record.logCompleteness,
|
|
508
|
+
sameSecondActivity: record.sameSecondActivity,
|
|
509
|
+
})),
|
|
510
|
+
rejected: result.rejected.map((record) => ({
|
|
511
|
+
code: record.code,
|
|
512
|
+
message: record.message,
|
|
513
|
+
...(record.logUrl === undefined ? {} : { logUrl: record.logUrl }),
|
|
514
|
+
...(record.integratedTime === undefined ? {} : { integratedTime: record.integratedTime }),
|
|
515
|
+
})),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
465
519
|
function renderActionsHtml(result: ReturnType<typeof verifyReceipts>): string {
|
|
466
|
-
const
|
|
520
|
+
const view = actionsViewModel(result);
|
|
521
|
+
const actionCount = view.receipts.length;
|
|
522
|
+
const rejectedCount = view.rejected.length;
|
|
523
|
+
const rows = view.receipts.map((record) => `
|
|
467
524
|
<tr>
|
|
468
525
|
<td>${escapeHtml(record.integratedTime)}</td>
|
|
469
526
|
<td>${escapeHtml(record.serviceIdentifier)}</td>
|
|
470
|
-
<td>${escapeHtml(record.
|
|
471
|
-
<td>${escapeHtml(record.
|
|
527
|
+
<td>${escapeHtml(record.actionType)}</td>
|
|
528
|
+
<td>${escapeHtml(record.resultStatus)}</td>
|
|
472
529
|
<td>${escapeHtml(record.status)}</td>
|
|
473
530
|
</tr>`).join("");
|
|
474
|
-
const rejected =
|
|
531
|
+
const rejected = view.rejected.map((record) => `
|
|
475
532
|
<li><strong>${escapeHtml(record.code)}</strong>: ${escapeHtml(record.message)}</li>`).join("");
|
|
476
533
|
|
|
477
534
|
return `<!doctype html>
|
|
@@ -479,23 +536,48 @@ function renderActionsHtml(result: ReturnType<typeof verifyReceipts>): string {
|
|
|
479
536
|
<head>
|
|
480
537
|
<meta charset="utf-8">
|
|
481
538
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
539
|
+
<meta http-equiv="refresh" content="2">
|
|
482
540
|
<title>Sello Actions</title>
|
|
483
541
|
<style>
|
|
484
542
|
body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 40px; color: #17201d; }
|
|
543
|
+
header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
|
|
544
|
+
h1 { margin: 0; font-size: 40px; line-height: 1.1; }
|
|
545
|
+
h2 { margin: 32px 0 8px; font-size: 20px; }
|
|
546
|
+
code { background: #eef3f0; border-radius: 4px; padding: 2px 6px; }
|
|
485
547
|
table { border-collapse: collapse; width: 100%; margin-top: 16px; }
|
|
486
548
|
th, td { border-bottom: 1px solid #d8dfdc; padding: 10px 8px; text-align: left; }
|
|
487
549
|
th { color: #52615b; font-weight: 600; }
|
|
550
|
+
.summary { color: #52615b; font-size: 15px; margin: 0; white-space: nowrap; }
|
|
488
551
|
.empty { color: #52615b; margin-top: 16px; }
|
|
552
|
+
.empty p { margin: 8px 0; }
|
|
553
|
+
@media (max-width: 720px) {
|
|
554
|
+
body { margin: 24px; }
|
|
555
|
+
header { display: block; }
|
|
556
|
+
h1 { font-size: 32px; }
|
|
557
|
+
.summary { margin-top: 8px; white-space: normal; }
|
|
558
|
+
table { display: block; overflow-x: auto; }
|
|
559
|
+
}
|
|
489
560
|
</style>
|
|
490
561
|
</head>
|
|
491
562
|
<body>
|
|
492
|
-
<
|
|
493
|
-
|
|
494
|
-
|
|
563
|
+
<header>
|
|
564
|
+
<h1>Sello Actions</h1>
|
|
565
|
+
<p class="summary">${escapeHtml(actionCountLabel(actionCount))}</p>
|
|
566
|
+
</header>
|
|
567
|
+
${rows ? `<table><thead><tr><th>Integrated time</th><th>Service</th><th>Action</th><th>Result</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>` : `<section class="empty"><p>No verified actions yet.</p><p>In another terminal, run <code>npx sello emit-demo</code>.</p></section>`}
|
|
568
|
+
${rejected ? `<h2>Rejected receipts</h2><p class="summary">${escapeHtml(rejectedCountLabel(rejectedCount))}</p><ul>${rejected}</ul>` : ""}
|
|
495
569
|
</body>
|
|
496
570
|
</html>`;
|
|
497
571
|
}
|
|
498
572
|
|
|
573
|
+
function actionCountLabel(count: number): string {
|
|
574
|
+
return count === 1 ? "1 verified action" : `${count} verified actions`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function rejectedCountLabel(count: number): string {
|
|
578
|
+
return count === 1 ? "1 rejected receipt" : `${count} rejected receipts`;
|
|
579
|
+
}
|
|
580
|
+
|
|
499
581
|
function verifyHttpProof(entry: ReturnType<typeof deserializeEntry>): boolean {
|
|
500
582
|
if (!isRecord(entry.proof)) {
|
|
501
583
|
return false;
|