@oneaddress/setup 2.6.0 → 2.7.0
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/index.js +234 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -856,7 +856,7 @@ var _R = ["\u2588\u2588\u2588\u2588\u2588\u2588 ", "\u2588\u2588 \u2588\u2588"
|
|
|
856
856
|
var _S = [" \u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588 ", " \u2588\u2588", " \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588 "];
|
|
857
857
|
var ONE_ROWS = Array.from({ length: 7 }, (_3, i) => [_O[i], _N[i], _E[i]].join(" "));
|
|
858
858
|
var ADDR_ROWS = Array.from({ length: 7 }, (_3, i) => [_A[i], _D2[i], _D2[i], _R[i], _E[i], _S[i], _S[i]].join(" "));
|
|
859
|
-
var WIZARD_VERSION = true ? "2.
|
|
859
|
+
var WIZARD_VERSION = true ? "2.7.0" : "?";
|
|
860
860
|
function printCompactHeader() {
|
|
861
861
|
const INNER = 42;
|
|
862
862
|
const TOP = fn("\u250C") + dm("\u2500".repeat(INNER)) + fn("\u2510");
|
|
@@ -2093,6 +2093,7 @@ export function startDashboard({ partnerName, port, onQuit, onReplay, stats }: T
|
|
|
2093
2093
|
// nothing, which is indistinguishable from a wedged UI.
|
|
2094
2094
|
let replaying = false;
|
|
2095
2095
|
screen.key(['r'], () => {
|
|
2096
|
+
if (promptOpen) return;
|
|
2096
2097
|
if (!onReplay || replaying) return;
|
|
2097
2098
|
replaying = true;
|
|
2098
2099
|
replayNote = 'Replaying\u2026';
|
|
@@ -2109,32 +2110,84 @@ export function startDashboard({ partnerName, port, onQuit, onReplay, stats }: T
|
|
|
2109
2110
|
.finally(() => { replaying = false; redraw(); });
|
|
2110
2111
|
});
|
|
2111
2112
|
|
|
2113
|
+
// A ONE-LINE PATH PROMPT DRAWN OVER THE DASHBOARD.
|
|
2114
|
+
//
|
|
2115
|
+
// \`screen.key\` handlers are GLOBAL in blessed and keep firing while a textbox
|
|
2116
|
+
// has focus, so [r], [d] and [q] would all act on keystrokes meant for the
|
|
2117
|
+
// path. \`promptOpen\` gates every one of them. Without it, typing a Windows
|
|
2118
|
+
// path containing a \`d\` arms a dismissal behind the prompt, and the \`q\` in
|
|
2119
|
+
// \`C:\\\\Users\\\\qmark\` quits the receiver mid-sentence.
|
|
2120
|
+
let promptOpen = false;
|
|
2121
|
+
function askForPath(initial: string, onAnswer: (dir: string) => void): void {
|
|
2122
|
+
promptOpen = true;
|
|
2123
|
+
const box = blessed.textbox({
|
|
2124
|
+
parent: screen,
|
|
2125
|
+
top: 'center', left: 'center', width: '80%', height: 3,
|
|
2126
|
+
border: { type: 'line' },
|
|
2127
|
+
style: { border: { fg: AMBER }, fg: CREAM },
|
|
2128
|
+
label: ' Export to which folder? Enter accepts, Esc cancels ',
|
|
2129
|
+
inputOnFocus: true,
|
|
2130
|
+
keys: true,
|
|
2131
|
+
});
|
|
2132
|
+
// PRE-FILLED WITH THE RESOLVED DEFAULT, not left empty. The answer to
|
|
2133
|
+
// "where does this go" has to be visible BEFORE the write, and an empty box
|
|
2134
|
+
// asks the partner to know something the dashboard never told them.
|
|
2135
|
+
box.setValue(initial);
|
|
2136
|
+
screen.render();
|
|
2137
|
+
box.readInput((_err, value) => {
|
|
2138
|
+
promptOpen = false;
|
|
2139
|
+
box.destroy();
|
|
2140
|
+
screen.render();
|
|
2141
|
+
// Escape gives no value. Treated as cancel, NOT as "use the default":
|
|
2142
|
+
// a partner who hits Esc has changed their mind about writing a file.
|
|
2143
|
+
if (typeof value !== 'string') { exportNote = 'Export cancelled.'; redraw(); return; }
|
|
2144
|
+
onAnswer(value.trim());
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2112
2148
|
// [e] EXPORTS THE FAULT LIST. Metadata only, no payloads: see exportHeld.
|
|
2113
2149
|
// Bound unconditionally for the same reason [r] is, so pressing it on a clean
|
|
2114
2150
|
// receiver says there is nothing to export rather than appearing to hang.
|
|
2151
|
+
//
|
|
2152
|
+
// IT ASKS WHERE, EVERY TIME (Tim's call). \`exportDir\` in
|
|
2153
|
+
// \`oneaddress.config.json\` and \`ONEADDRESS_EXPORT_DIR\` still decide the
|
|
2154
|
+
// DEFAULT, but they were the only ways to choose and neither is visible from
|
|
2155
|
+
// the dashboard - reported from a real run as "it looks like export location
|
|
2156
|
+
// cannot be picked". Pressing Enter on the pre-filled prompt is byte-for-byte
|
|
2157
|
+
// the old behaviour, so nothing that worked before needs changing.
|
|
2115
2158
|
screen.key(['e'], () => {
|
|
2159
|
+
if (promptOpen) return;
|
|
2116
2160
|
if (heldCount() === 0) {
|
|
2117
2161
|
replayNote = 'Nothing held, so nothing to export.';
|
|
2118
2162
|
redraw();
|
|
2119
2163
|
return;
|
|
2120
2164
|
}
|
|
2165
|
+
askForPath(config.exportDir || process.cwd(), (dir) => { writeExport(dir); redraw(); });
|
|
2166
|
+
});
|
|
2167
|
+
|
|
2168
|
+
/**
|
|
2169
|
+
* Write the fault export, and say where it landed.
|
|
2170
|
+
*
|
|
2171
|
+
* Shared by [e] and [d] rather than duplicated, because [d] exports FIRST and
|
|
2172
|
+
* the two must agree on the destination and on the message. Returns whether
|
|
2173
|
+
* it succeeded, which is what lets [d] refuse to discard anything after a
|
|
2174
|
+
* failed write.
|
|
2175
|
+
*/
|
|
2176
|
+
function writeExport(dir: string): boolean {
|
|
2121
2177
|
try {
|
|
2122
|
-
|
|
2123
|
-
// \`ONEADDRESS_EXPORT_DIR\`, then the working directory, and returns an
|
|
2124
|
-
// ABSOLUTE path. Passing \`process.cwd()\` here is what made the setting
|
|
2125
|
-
// unreachable from the one key that uses it.
|
|
2126
|
-
const { path, count } = exportHeld();
|
|
2178
|
+
const { path, count } = exportHeld(dir);
|
|
2127
2179
|
exportNote = \`Saved \${count} to \${path}\`;
|
|
2128
2180
|
replayNote = '';
|
|
2181
|
+
return true;
|
|
2129
2182
|
} catch (err) {
|
|
2130
2183
|
// NAMES THE DIRECTORY IT TRIED. "EACCES" on its own sends a partner to
|
|
2131
2184
|
// search for a file that was never written.
|
|
2132
|
-
exportNote = \`Export FAILED writing to \${
|
|
2185
|
+
exportNote = \`Export FAILED writing to \${dir || process.cwd()}: \` +
|
|
2133
2186
|
\`\${err instanceof Error ? err.message : String(err)}\`;
|
|
2134
2187
|
replayNote = '';
|
|
2188
|
+
return false;
|
|
2135
2189
|
}
|
|
2136
|
-
|
|
2137
|
-
});
|
|
2190
|
+
}
|
|
2138
2191
|
|
|
2139
2192
|
// [d] DISMISSES. TWO PRESSES, and the first one only asks.
|
|
2140
2193
|
//
|
|
@@ -2145,10 +2198,19 @@ export function startDashboard({ partnerName, port, onQuit, onReplay, stats }: T
|
|
|
2145
2198
|
// working correctly, with nothing on screen that clears it. Reported from a
|
|
2146
2199
|
// real run: "I do wish the conformance wouldn't come up as a fault."
|
|
2147
2200
|
//
|
|
2148
|
-
//
|
|
2149
|
-
//
|
|
2150
|
-
//
|
|
2151
|
-
//
|
|
2201
|
+
// THE RECEIVER DOES RECOGNISE THE PROBE NOW, AND THIS PARAGRAPH USED TO SAY
|
|
2202
|
+
// OTHERWISE. It argued the receiver could not, because "everything that
|
|
2203
|
+
// identifies one is chosen by the sender". That over-weighted the attacker:
|
|
2204
|
+
// anyone able to forge a signed dispatch already holds the webhook secret and
|
|
2205
|
+
// can make this receiver APPLY a bogus address change, so hiding a quarantine
|
|
2206
|
+
// row is not the prize. The real question was signed versus unsigned, and
|
|
2207
|
+
// \`conformance: true\` rides inside the signed body. A drill is classified, not
|
|
2208
|
+
// suppressed - still written, still counted, drawn calmly rather than as an
|
|
2209
|
+
// alarm - so [d] is no longer the only way to clear a passing run.
|
|
2210
|
+
//
|
|
2211
|
+
// IT STILL EXISTS, because a drill is not the only thing that gets held: a
|
|
2212
|
+
// genuinely undecryptable dispatch from a key that has since been rotated is
|
|
2213
|
+
// never going to replay, and without this the panel stays red forever.
|
|
2152
2214
|
//
|
|
2153
2215
|
// IRREVERSIBLE, SO IT IS CONFIRMED. Dismissal discards the held payload, which
|
|
2154
2216
|
// is the point (see \`dismissHeld\`) and also means a mistaken press cannot be
|
|
@@ -2163,6 +2225,7 @@ export function startDashboard({ partnerName, port, onQuit, onReplay, stats }: T
|
|
|
2163
2225
|
// that was described makes the question and the action the same question.
|
|
2164
2226
|
let dismissArmedFor: number | null = null;
|
|
2165
2227
|
screen.key(['d'], () => {
|
|
2228
|
+
if (promptOpen) return;
|
|
2166
2229
|
const held = heldCount();
|
|
2167
2230
|
if (held === 0) {
|
|
2168
2231
|
dismissArmedFor = null;
|
|
@@ -2179,9 +2242,29 @@ export function startDashboard({ partnerName, port, onQuit, onReplay, stats }: T
|
|
|
2179
2242
|
return;
|
|
2180
2243
|
}
|
|
2181
2244
|
dismissArmedFor = null;
|
|
2245
|
+
// EXPORTED BEFORE IT IS DISCARDED, ALWAYS.
|
|
2246
|
+
//
|
|
2247
|
+
// \`exportHeld\` reads \`heldDispatches()\`, which filters out dismissed rows,
|
|
2248
|
+
// so [d] then [e] exported NOTHING and the record was unrecoverable from the
|
|
2249
|
+
// UI. The payload is meant to go - it is an encrypted consumer address on a
|
|
2250
|
+
// third party's disk - but the metadata is the part you would send to
|
|
2251
|
+
// support, and dismissing destroyed exactly that. Reported from a real run:
|
|
2252
|
+
// "if they dismiss a fault, does that mean they cant export and record it?"
|
|
2253
|
+
//
|
|
2254
|
+
// WRITTEN WITHOUT ASKING, unlike [e]. The partner is one keypress from
|
|
2255
|
+
// discarding the evidence and an extra prompt here is a prompt at the worst
|
|
2256
|
+
// possible moment; the default destination is the right answer for a record
|
|
2257
|
+
// nobody asked for. A FAILED write ABORTS the dismissal rather than
|
|
2258
|
+
// proceeding, because "we could not save the record, so we destroyed it" is
|
|
2259
|
+
// the one outcome nothing recovers from.
|
|
2260
|
+
if (!writeExport(config.exportDir || process.cwd())) {
|
|
2261
|
+
replayNote = 'Dismiss ABORTED: nothing was discarded because the record could not be saved.';
|
|
2262
|
+
redraw();
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2182
2265
|
try {
|
|
2183
2266
|
const n = dismissHeld();
|
|
2184
|
-
replayNote = \`Dismissed \${n}. Nothing left held.\`;
|
|
2267
|
+
replayNote = \`Dismissed \${n}. Nothing left held - the record was exported first.\`;
|
|
2185
2268
|
} catch (err) {
|
|
2186
2269
|
replayNote = \`Dismiss failed: \${err instanceof Error ? err.message : String(err)}\`;
|
|
2187
2270
|
}
|
|
@@ -2189,6 +2272,7 @@ export function startDashboard({ partnerName, port, onQuit, onReplay, stats }: T
|
|
|
2189
2272
|
});
|
|
2190
2273
|
|
|
2191
2274
|
screen.key(['q', 'C-c'], () => {
|
|
2275
|
+
if (promptOpen) return;
|
|
2192
2276
|
detach();
|
|
2193
2277
|
screen.destroy();
|
|
2194
2278
|
onQuit();
|
|
@@ -6456,8 +6540,9 @@ export async function ask(prompt: string): Promise<string> {
|
|
|
6456
6540
|
* Show what this receiver holds: the roster, and every change it has applied.
|
|
6457
6541
|
*
|
|
6458
6542
|
* Usage:
|
|
6459
|
-
* npm run show \u2014
|
|
6460
|
-
* npm run show 100 \u2014
|
|
6543
|
+
* npm run show \u2014 a LIVE branded view, refreshing as changes land
|
|
6544
|
+
* npm run show 100 \u2014 keep 100 changes on screen instead of 20
|
|
6545
|
+
* npm run show > f.txt \u2014 plain one-shot text, for a file or a pipe
|
|
6461
6546
|
*
|
|
6462
6547
|
* ## Why this script exists
|
|
6463
6548
|
*
|
|
@@ -6468,6 +6553,23 @@ export async function ask(prompt: string): Promise<string> {
|
|
|
6468
6553
|
* dispatch: "where do I look to prove it end to end?" The dashboard's LAST
|
|
6469
6554
|
* CHANGE panel shows one change and nothing showed the rest.
|
|
6470
6555
|
*
|
|
6556
|
+
* ## Why it STREAMS rather than printing once
|
|
6557
|
+
*
|
|
6558
|
+
* The first version printed a snapshot and exited, which answers "did it land"
|
|
6559
|
+
* only if you already knew to run it again. Watching a dispatch arrive meant
|
|
6560
|
+
* re-running it by hand and diffing two screenfuls in your head. It holds the
|
|
6561
|
+
* screen now and polls, so a change appears as it is applied.
|
|
6562
|
+
*
|
|
6563
|
+
* POLLED, NOT SUBSCRIBED, and that is forced rather than lazy: this is a
|
|
6564
|
+
* SEPARATE PROCESS from the receiver, so there is no event to listen to. SQLite
|
|
6565
|
+
* readers do not block writers, so a poll costs the running receiver nothing.
|
|
6566
|
+
* The screen is only redrawn when the rendered content actually CHANGES, so an
|
|
6567
|
+
* idle receiver is not repainting a terminal once a second.
|
|
6568
|
+
*
|
|
6569
|
+
* NON-TTY FALLS BACK TO ONE-SHOT TEXT. \`npm run show > out.txt\`, a pipe, and a
|
|
6570
|
+
* CI log have no terminal to hold, and a blessed screen drawn into a pipe
|
|
6571
|
+
* produces escape-code soup. Same rule the dashboard itself follows.
|
|
6572
|
+
*
|
|
6471
6573
|
* It prompts for the password for the same reason \`src/index.ts\` does, and for
|
|
6472
6574
|
* the same reason it must do so BEFORE importing the store: the database
|
|
6473
6575
|
* derives its keys on import.
|
|
@@ -6478,8 +6580,17 @@ export async function ask(prompt: string): Promise<string> {
|
|
|
6478
6580
|
* in the connector's own store.
|
|
6479
6581
|
*/
|
|
6480
6582
|
import 'dotenv/config';
|
|
6583
|
+
import blessed from 'blessed';
|
|
6481
6584
|
import { config } from '../src/config.js';
|
|
6482
6585
|
import { ask, databaseIsLocked } from '../src/unlock.js';
|
|
6586
|
+
import { HEX, ONE_ROWS, ADDRESS_ROWS, terminalFitsFullMark } from '../src/brand.js';
|
|
6587
|
+
|
|
6588
|
+
const AMBER = HEX.amber.toLowerCase();
|
|
6589
|
+
const CREAM = HEX.cream.toLowerCase();
|
|
6590
|
+
const DIM = '#8a7f6a';
|
|
6591
|
+
|
|
6592
|
+
/** Escape blessed's tag syntax so a customer's name can never inject markup. */
|
|
6593
|
+
const esc = (s: unknown): string => String(s ?? '').replace(/[{}]/g, '');
|
|
6483
6594
|
|
|
6484
6595
|
function fmt(a: Record<string, string> | null): string {
|
|
6485
6596
|
if (!a) return '(nothing on file)';
|
|
@@ -6495,18 +6606,32 @@ async function askPassword(): Promise<void> {
|
|
|
6495
6606
|
if (answer) process.env.ONEADDRESS_DB_PASSPHRASE = answer;
|
|
6496
6607
|
}
|
|
6497
6608
|
|
|
6498
|
-
|
|
6499
|
-
if (config.mode === 'inbox') {
|
|
6500
|
-
console.log('\\n This receiver is in inbox mode: it holds no customer records and no key.');
|
|
6501
|
-
console.log(' Your connector applied the change and holds the result. Look there.\\n');
|
|
6502
|
-
return;
|
|
6503
|
-
}
|
|
6609
|
+
type Store = typeof import('../src/store.js');
|
|
6504
6610
|
|
|
6505
|
-
|
|
6611
|
+
/** The roster, as display rows. */
|
|
6612
|
+
function rosterRows(store: Store): string[] {
|
|
6613
|
+
return store.allCustomers().map((c) => {
|
|
6614
|
+
let addr: Record<string, string> | null = null;
|
|
6615
|
+
try { addr = JSON.parse(c.address) as Record<string, string>; } catch { addr = null; }
|
|
6616
|
+
return \` \${esc(c.account_number).padEnd(20)} \${esc(c.name).padEnd(24)} \${esc(fmt(addr))}\`;
|
|
6617
|
+
});
|
|
6618
|
+
}
|
|
6506
6619
|
|
|
6507
|
-
|
|
6508
|
-
|
|
6620
|
+
/** Applied changes, newest first, as display rows with both sides. */
|
|
6621
|
+
function changeRows(store: Store, limit: number): string[] {
|
|
6622
|
+
const out: string[] = [];
|
|
6623
|
+
for (const h of store.addressHistory(limit)) {
|
|
6624
|
+
const prev = h.prev_address as unknown as Record<string, string> | null;
|
|
6625
|
+
const now = h.address as unknown as Record<string, string>;
|
|
6626
|
+
out.push(\` {\${DIM}-fg}\${esc(h.recorded_at)}{/} {\${CREAM}-fg}\${esc(h.account_number)}{/} \${esc(h.name)}\`);
|
|
6627
|
+
out.push(\` {\${DIM}-fg}was:{/} \${esc(fmt(prev))}\`);
|
|
6628
|
+
out.push(\` {\${AMBER}-fg}now:{/} \${esc(fmt(now))}\`);
|
|
6629
|
+
}
|
|
6630
|
+
return out;
|
|
6631
|
+
}
|
|
6509
6632
|
|
|
6633
|
+
/** One-shot plain text. The pipe, the file, the CI log. */
|
|
6634
|
+
function printOnce(store: Store, limit: number): void {
|
|
6510
6635
|
const roster = store.allCustomers();
|
|
6511
6636
|
console.log(\`\\n ON FILE (\${roster.length})\\n\`);
|
|
6512
6637
|
for (const c of roster) {
|
|
@@ -6514,7 +6639,6 @@ async function main(): Promise<void> {
|
|
|
6514
6639
|
try { addr = JSON.parse(c.address) as Record<string, string>; } catch { addr = null; }
|
|
6515
6640
|
console.log(\` \${c.account_number.padEnd(14)} \${c.name.padEnd(24)} \${fmt(addr)}\`);
|
|
6516
6641
|
}
|
|
6517
|
-
|
|
6518
6642
|
const changes = store.addressHistory(limit);
|
|
6519
6643
|
console.log(\`\\n APPLIED CHANGES (\${changes.length}, newest first)\\n\`);
|
|
6520
6644
|
if (changes.length === 0) {
|
|
@@ -6529,6 +6653,86 @@ async function main(): Promise<void> {
|
|
|
6529
6653
|
console.log('');
|
|
6530
6654
|
}
|
|
6531
6655
|
|
|
6656
|
+
/** The live view: the mark, the roster, the changes, refreshed as they land. */
|
|
6657
|
+
function stream(store: Store, limit: number): void {
|
|
6658
|
+
const screen = blessed.screen({ smartCSR: true, title: 'What this receiver holds \u2014 OneAddress' });
|
|
6659
|
+
|
|
6660
|
+
// Same rule as the dashboard: the pixel wordmark needs 88 columns, and below
|
|
6661
|
+
// that it wraps and reads as broken rather than as large.
|
|
6662
|
+
const wide = terminalFitsFullMark(Number(screen.width));
|
|
6663
|
+
const markHeight = wide ? 9 : 3;
|
|
6664
|
+
blessed.box({
|
|
6665
|
+
parent: screen, top: 0, left: 0, width: '100%', height: markHeight,
|
|
6666
|
+
tags: true, padding: { left: 2 },
|
|
6667
|
+
content: wide
|
|
6668
|
+
? ONE_ROWS.map((row, i) => \`{\${CREAM}-fg}\${row}{/} {\${AMBER}-fg}\${ADDRESS_ROWS[i]}{/}\`).join('\\n')
|
|
6669
|
+
: \`{\${CREAM}-fg}{bold}One{/bold}{/}{\${AMBER}-fg}{bold}Address{/bold}{/}\`,
|
|
6670
|
+
});
|
|
6671
|
+
|
|
6672
|
+
const rosterBox = blessed.box({
|
|
6673
|
+
parent: screen, top: markHeight, left: 0, width: '100%', height: 'shrink',
|
|
6674
|
+
tags: true, padding: { left: 1, right: 1 },
|
|
6675
|
+
border: { type: 'line' }, style: { border: { fg: AMBER } },
|
|
6676
|
+
label: ' ON FILE ',
|
|
6677
|
+
});
|
|
6678
|
+
|
|
6679
|
+
const changesBox = blessed.box({
|
|
6680
|
+
parent: screen, top: markHeight + 3, left: 0, width: '100%', bottom: 1,
|
|
6681
|
+
tags: true, padding: { left: 1, right: 1 }, scrollable: true, alwaysScroll: true,
|
|
6682
|
+
border: { type: 'line' }, style: { border: { fg: AMBER } },
|
|
6683
|
+
label: ' APPLIED CHANGES \u2014 newest first ',
|
|
6684
|
+
});
|
|
6685
|
+
|
|
6686
|
+
blessed.box({
|
|
6687
|
+
parent: screen, bottom: 0, left: 0, width: '100%', height: 1,
|
|
6688
|
+
tags: true, padding: { left: 2 },
|
|
6689
|
+
content: \`{\${DIM}-fg}live \u2014 updates as dispatches land{/} {\${CREAM}-fg}[q]{/} quit\`,
|
|
6690
|
+
});
|
|
6691
|
+
|
|
6692
|
+
// ONLY REDRAWN WHEN THE CONTENT CHANGES. A poll that repaints unconditionally
|
|
6693
|
+
// makes an idle receiver flicker once a second and destroys any scrollback
|
|
6694
|
+
// position the operator had.
|
|
6695
|
+
let last = '';
|
|
6696
|
+
const tick = (): void => {
|
|
6697
|
+
const roster = rosterRows(store);
|
|
6698
|
+
const changes = changeRows(store, limit);
|
|
6699
|
+
const next = roster.join('\\n') + '\\u0000' + changes.join('\\n');
|
|
6700
|
+
if (next === last) return;
|
|
6701
|
+
last = next;
|
|
6702
|
+
rosterBox.height = roster.length + 2;
|
|
6703
|
+
rosterBox.setContent(roster.join('\\n') || ' (nobody on file yet)');
|
|
6704
|
+
changesBox.top = markHeight + roster.length + 2;
|
|
6705
|
+
changesBox.setContent(changes.join('\\n') || ' None yet. Send a dispatch and this fills in.');
|
|
6706
|
+
screen.render();
|
|
6707
|
+
};
|
|
6708
|
+
|
|
6709
|
+
screen.key(['q', 'C-c', 'escape'], () => { screen.destroy(); process.exit(0); });
|
|
6710
|
+
tick();
|
|
6711
|
+
screen.render();
|
|
6712
|
+
// DELIBERATELY NOT unref'd. An unref'd timer does not hold the event loop
|
|
6713
|
+
// open, and with the screen drawn there is nothing else pending, so the
|
|
6714
|
+
// process would exit the instant it finished painting.
|
|
6715
|
+
setInterval(tick, 1000);
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
async function main(): Promise<void> {
|
|
6719
|
+
if (config.mode === 'inbox') {
|
|
6720
|
+
console.log('\\n This receiver is in inbox mode: it holds no customer records and no key.');
|
|
6721
|
+
console.log(' Your connector applied the change and holds the result. Look there.\\n');
|
|
6722
|
+
return;
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
await askPassword();
|
|
6726
|
+
|
|
6727
|
+
const limit = Number(process.argv[2]) > 0 ? Number(process.argv[2]) : 20;
|
|
6728
|
+
const store = await import('../src/store.js');
|
|
6729
|
+
|
|
6730
|
+
// A pipe, a file or a CI log has no terminal to hold, and a blessed screen
|
|
6731
|
+
// drawn into one produces escape-code soup rather than output.
|
|
6732
|
+
if (!process.stdout.isTTY) { printOnce(store, limit); return; }
|
|
6733
|
+
stream(store, limit);
|
|
6734
|
+
}
|
|
6735
|
+
|
|
6532
6736
|
void main().catch((err: unknown) => {
|
|
6533
6737
|
console.error(err instanceof Error ? err.message : String(err));
|
|
6534
6738
|
process.exit(1);
|
|
@@ -11903,7 +12107,7 @@ async function scaffold(platform, outputDir, partnerId, webhookSecret, webhookUr
|
|
|
11903
12107
|
|
|
11904
12108
|
// src/register.ts
|
|
11905
12109
|
var import_node_crypto2 = require("crypto");
|
|
11906
|
-
var PKG_VERSION = true ? "2.
|
|
12110
|
+
var PKG_VERSION = true ? "2.7.0" : "dev";
|
|
11907
12111
|
var REGISTER_URL = "https://partners.oneaddress.io/api/partner/installs";
|
|
11908
12112
|
function hmacSha256(secret, message) {
|
|
11909
12113
|
return (0, import_node_crypto2.createHmac)("sha256", secret).update(message).digest("hex");
|
|
@@ -12972,6 +13176,9 @@ OneAddress and a leak of either must not also hand somebody your customers' addr
|
|
|
12972
13176
|
"No private key is collected for an inbox receiver. It belongs in your connector,\nwhich is the only process that decrypts. This one is meant never to hold one, and\nit refuses to start if it finds one in its environment."
|
|
12973
13177
|
);
|
|
12974
13178
|
} else {
|
|
13179
|
+
M2.info(
|
|
13180
|
+
"It looks like one of these:\n -----BEGIN PRIVATE KEY----- (a .pem file, paste the whole thing)\n MIGHAgEAMB... (the base64 body, copied from My Profile)\n C:\\keys\\oneaddress.pem (or just the path to the file)\nThis is your PRIVATE key, not the public one shown next to it in the portal."
|
|
13181
|
+
);
|
|
12975
13182
|
const privateKeyRaw = await he({
|
|
12976
13183
|
message: "Your ECDH Private Key",
|
|
12977
13184
|
placeholder: "Paste the base64 key body from My Profile, or enter a path to the .pem file",
|