@defdone/annon 0.1.0-alpha.1 → 0.1.0-alpha.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/CHANGELOG.md +26 -0
- package/CLI.md +173 -13
- package/README.md +47 -10
- package/dist/api/annon-types.d.ts +2 -1
- package/dist/api/annon.js +9 -2
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/login-bundle.js +61 -60
- package/dist/browser/login-dom.d.ts +13 -0
- package/dist/browser/login-dom.js +71 -0
- package/dist/browser/login-dropdown.d.ts +32 -0
- package/dist/browser/login-dropdown.js +129 -0
- package/dist/browser/login-format.d.ts +16 -0
- package/dist/browser/login-format.js +52 -0
- package/dist/browser/login-status.d.ts +70 -0
- package/dist/browser/login-status.js +85 -0
- package/dist/browser/login-steps.d.ts +6 -0
- package/dist/browser/login-steps.js +35 -0
- package/dist/browser/login-terms.d.ts +10 -0
- package/dist/browser/login-terms.js +69 -0
- package/dist/browser/login-view.d.ts +34 -0
- package/dist/browser/login-view.js +227 -0
- package/dist/browser/login-wallet.d.ts +28 -0
- package/dist/browser/login-wallet.js +179 -0
- package/dist/browser/login.js +154 -142
- package/dist/browser/purchase-bundle.js +29 -29
- package/dist/browser/purchases.d.ts +4 -0
- package/dist/browser/purchases.js +14 -0
- package/dist/browser/reconcile.d.ts +41 -0
- package/dist/browser/reconcile.js +77 -0
- package/dist/node/adapter-checks.d.ts +3 -1
- package/dist/node/adapter-checks.js +3 -1
- package/dist/node/adapter.js +10 -3
- package/dist/node/agent.js +2 -1
- package/dist/node/approval-status.d.ts +34 -0
- package/dist/node/approval-status.js +71 -0
- package/dist/node/backup-secret-init.d.ts +9 -0
- package/dist/node/backup-secret-init.js +103 -0
- package/dist/node/backup-secret.d.ts +35 -0
- package/dist/node/backup-secret.js +104 -0
- package/dist/node/browser-login.d.ts +2 -0
- package/dist/node/browser-login.js +95 -16
- package/dist/node/buy-commands.js +17 -9
- package/dist/node/buy-flow.d.ts +18 -0
- package/dist/node/buy-flow.js +89 -0
- package/dist/node/cli-flags.d.ts +10 -0
- package/dist/node/cli-flags.js +68 -0
- package/dist/node/cli.d.ts +9 -1
- package/dist/node/cli.js +87 -95
- package/dist/node/command-context.js +1 -1
- package/dist/node/config.d.ts +4 -0
- package/dist/node/config.js +6 -2
- package/dist/node/core-adapter.js +4 -0
- package/dist/node/login-page.d.ts +7 -0
- package/dist/node/login-page.js +77 -0
- package/dist/node/main.js +20 -2
- package/dist/node/output/discovery.d.ts +23 -0
- package/dist/node/output/discovery.js +52 -0
- package/dist/node/output/human.d.ts +10 -0
- package/dist/node/output/human.js +46 -0
- package/dist/node/output/purchase.d.ts +10 -0
- package/dist/node/output/purchase.js +78 -0
- package/dist/node/output/text.d.ts +10 -0
- package/dist/node/output/text.js +43 -0
- package/dist/node/purchase-commands.d.ts +2 -2
- package/dist/node/purchase-commands.js +14 -6
- package/dist/node/purchase-ref.d.ts +15 -0
- package/dist/node/purchase-ref.js +93 -0
- package/dist/node/saved-state.d.ts +4 -0
- package/dist/node/saved-state.js +50 -0
- package/dist/node/shell/complete.d.ts +2 -0
- package/dist/node/shell/complete.js +17 -0
- package/dist/node/shell/session.d.ts +20 -0
- package/dist/node/shell/session.js +219 -0
- package/dist/node/shell/welcome.d.ts +9 -0
- package/dist/node/shell/welcome.js +39 -0
- package/dist/node/shell/words.d.ts +1 -0
- package/dist/node/shell/words.js +36 -0
- package/dist/node/shops.d.ts +5 -0
- package/dist/node/shops.js +22 -0
- package/dist/node/version.d.ts +1 -0
- package/dist/node/version.js +3 -0
- package/dist/node/wallets.d.ts +1 -1
- package/dist/node/wallets.js +2 -10
- package/dist/pay/purchase.d.ts +2 -1
- package/dist/wallet/uncertain.d.ts +6 -3
- package/dist/wallet/uncertain.js +4 -1
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
5
|
import { TransactionUncertain } from '../wallet/uncertain.js';
|
|
6
|
+
import { APPROVED_LINE, cancelledError, expiredError, failureLine, isPageReport, openingLine, sendUncertain, usableHash, waitingLine, } from './approval-status.js';
|
|
7
|
+
import { LOGIN_PAGE_CSS, LOGIN_PAGE_HTML } from './login-page.js';
|
|
6
8
|
const equal = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
7
9
|
/**
|
|
8
10
|
* The approval page talks only to this loopback server, the NEAR RPC its Wallet Selector uses
|
|
@@ -12,6 +14,7 @@ const equal = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingS
|
|
|
12
14
|
*/
|
|
13
15
|
export const LOGIN_PAGE_CSP = "default-src 'none'; script-src 'self'; connect-src 'self' https://free.rpc.fastnear.com https://test.rpc.fastnear.com https://h4n.app; frame-src https://hot-labs.org; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
|
|
14
16
|
export async function startBrowserSession(o) {
|
|
17
|
+
const notify = o.notify ?? ((line) => void process.stderr.write(`${line}\n`));
|
|
15
18
|
let asset = o.asset;
|
|
16
19
|
if (asset === undefined) {
|
|
17
20
|
try {
|
|
@@ -41,7 +44,22 @@ export async function startBrowserSession(o) {
|
|
|
41
44
|
.map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
|
|
42
45
|
.join(',')}}`
|
|
43
46
|
: JSON.stringify(v);
|
|
47
|
+
/** Ends a request whose transaction may be on chain, with the CLI's own warning. */
|
|
48
|
+
const uncertain = (p, txHash) => {
|
|
49
|
+
const { line, error } = sendUncertain(p.action, txHash);
|
|
50
|
+
notify(line);
|
|
51
|
+
return error;
|
|
52
|
+
};
|
|
53
|
+
/** A transaction request whose send started, or that returned transaction evidence at all. */
|
|
54
|
+
const sent = (p) => p.sending === true ||
|
|
55
|
+
p.action.operation === 'near.signTransaction';
|
|
44
56
|
let pending;
|
|
57
|
+
const followers = new Set();
|
|
58
|
+
const advance = (action = null) => {
|
|
59
|
+
for (const response of followers)
|
|
60
|
+
response.end(JSON.stringify({ action }));
|
|
61
|
+
followers.clear();
|
|
62
|
+
};
|
|
45
63
|
const server = createServer((req, res) => {
|
|
46
64
|
void (async () => {
|
|
47
65
|
const headers = {
|
|
@@ -67,8 +85,12 @@ export async function startBrowserSession(o) {
|
|
|
67
85
|
!equal(req.headers.authorization ?? '', `Bearer ${capability}`))
|
|
68
86
|
return void send(403, '{}');
|
|
69
87
|
if (path === '/api/action' && ['GET', 'POST'].includes(req.method ?? ''))
|
|
70
|
-
return void send(200, JSON.stringify({
|
|
71
|
-
|
|
88
|
+
return void send(200, JSON.stringify({
|
|
89
|
+
pairingCode,
|
|
90
|
+
action: pending?.action ?? null,
|
|
91
|
+
sendStarted: pending?.sending === true,
|
|
92
|
+
}));
|
|
93
|
+
if (!['/api/result', '/api/report'].includes(path) || req.method !== 'POST')
|
|
72
94
|
return void send(404, '{}');
|
|
73
95
|
let size = 0;
|
|
74
96
|
const parts = [];
|
|
@@ -79,6 +101,31 @@ export async function startBrowserSession(o) {
|
|
|
79
101
|
parts.push(chunk);
|
|
80
102
|
}
|
|
81
103
|
const b = JSON.parse(Buffer.concat(parts).toString());
|
|
104
|
+
if (path === '/api/report') {
|
|
105
|
+
// A fixed reason for the current, unconsumed request. Only `cancelled` and
|
|
106
|
+
// `send_uncertain` end it, and never as cancelled once the page started a send.
|
|
107
|
+
const p = pending;
|
|
108
|
+
if (!p || p.busy || b.id !== p.action.id || !isPageReport(b.reason))
|
|
109
|
+
return void send(409, '{}');
|
|
110
|
+
if (b.reason === 'send_started') {
|
|
111
|
+
// One send per request: a reloaded or second page cannot start another.
|
|
112
|
+
if (p.sending)
|
|
113
|
+
return void send(409, '{}');
|
|
114
|
+
p.sending = true;
|
|
115
|
+
}
|
|
116
|
+
else if (b.reason === 'cancelled' || b.reason === 'send_uncertain') {
|
|
117
|
+
clearTimeout(p.timer);
|
|
118
|
+
pending = undefined;
|
|
119
|
+
armIdle();
|
|
120
|
+
p.reject(p.sending || b.reason === 'send_uncertain' ? uncertain(p) : cancelledError());
|
|
121
|
+
}
|
|
122
|
+
else if (!p.sending && p.failure !== b.reason) {
|
|
123
|
+
// After a send started no failure can be retried, so none is announced as one.
|
|
124
|
+
p.failure = b.reason;
|
|
125
|
+
notify(failureLine(b.reason, p.action.expiresAt - Date.now()));
|
|
126
|
+
}
|
|
127
|
+
return void send(200, '{}');
|
|
128
|
+
}
|
|
82
129
|
const key = canonical(b.result);
|
|
83
130
|
const previous = results.get(b.id);
|
|
84
131
|
if (previous && Date.now() < previous.expiresAt) {
|
|
@@ -104,9 +151,17 @@ export async function startBrowserSession(o) {
|
|
|
104
151
|
pending = undefined;
|
|
105
152
|
armIdle();
|
|
106
153
|
}
|
|
107
|
-
|
|
154
|
+
notify(APPROVED_LINE);
|
|
108
155
|
complete(200);
|
|
109
|
-
|
|
156
|
+
if (b.follow === true) {
|
|
157
|
+
res.writeHead(200, { ...headers, 'content-type': 'application/json' });
|
|
158
|
+
res.flushHeaders();
|
|
159
|
+
followers.add(res);
|
|
160
|
+
res.on('close', () => followers.delete(res));
|
|
161
|
+
}
|
|
162
|
+
else
|
|
163
|
+
send(200, '{}');
|
|
164
|
+
p.resolve(b.result);
|
|
110
165
|
}
|
|
111
166
|
catch (error) {
|
|
112
167
|
clearTimeout(p.timer);
|
|
@@ -114,7 +169,13 @@ export async function startBrowserSession(o) {
|
|
|
114
169
|
pending = undefined;
|
|
115
170
|
armIdle();
|
|
116
171
|
}
|
|
117
|
-
|
|
172
|
+
// After a send, a result that cannot be verified is never evidence of success or of
|
|
173
|
+
// failure: the request ends as uncertain, with any usable hash, so it stays saved.
|
|
174
|
+
p.reject(sent(p)
|
|
175
|
+
? uncertain(p, usableHash(error instanceof TransactionUncertain ? error.txHash : b?.result?.txHash))
|
|
176
|
+
: error instanceof TransactionUncertain
|
|
177
|
+
? error
|
|
178
|
+
: new Error('wallet result rejected'));
|
|
118
179
|
complete(400);
|
|
119
180
|
send(400, '{}');
|
|
120
181
|
}
|
|
@@ -124,8 +185,10 @@ export async function startBrowserSession(o) {
|
|
|
124
185
|
return void send(405, '{}');
|
|
125
186
|
if (path === '/app.js')
|
|
126
187
|
return void send(200, asset, 'text/javascript');
|
|
188
|
+
if (path === '/app.css')
|
|
189
|
+
return void send(200, LOGIN_PAGE_CSS, 'text/css; charset=utf-8');
|
|
127
190
|
if (path === '/' || path === '/callback')
|
|
128
|
-
return void send(200,
|
|
191
|
+
return void send(200, LOGIN_PAGE_HTML, 'text/html; charset=utf-8');
|
|
129
192
|
send(404, '{}');
|
|
130
193
|
})().catch(() => {
|
|
131
194
|
if (!res.headersSent)
|
|
@@ -148,8 +211,19 @@ export async function startBrowserSession(o) {
|
|
|
148
211
|
pending.reject(new Error('session closed'));
|
|
149
212
|
pending = undefined;
|
|
150
213
|
}
|
|
214
|
+
const flushed = [...followers].map((response) => new Promise((resolve) => response.end(JSON.stringify({ action: null }), resolve)));
|
|
215
|
+
followers.clear();
|
|
216
|
+
const stopped = new Promise((resolve) => server.close(() => resolve()));
|
|
217
|
+
let flushTimer;
|
|
218
|
+
await Promise.race([
|
|
219
|
+
Promise.all(flushed),
|
|
220
|
+
new Promise((resolve) => {
|
|
221
|
+
flushTimer = setTimeout(resolve, 1000);
|
|
222
|
+
}),
|
|
223
|
+
]);
|
|
224
|
+
clearTimeout(flushTimer);
|
|
151
225
|
server.closeAllConnections();
|
|
152
|
-
await
|
|
226
|
+
await stopped;
|
|
153
227
|
};
|
|
154
228
|
let timeout;
|
|
155
229
|
const armIdle = () => {
|
|
@@ -161,6 +235,9 @@ export async function startBrowserSession(o) {
|
|
|
161
235
|
};
|
|
162
236
|
armIdle();
|
|
163
237
|
const url = `${origin}/#${capability}`;
|
|
238
|
+
// The capability-free page URL: in open mode the page bootstraps the capability once.
|
|
239
|
+
if (!o.noOpen)
|
|
240
|
+
notify(openingLine(`${origin}/`));
|
|
164
241
|
if (!o.noOpen)
|
|
165
242
|
(o.open ??
|
|
166
243
|
((target) => {
|
|
@@ -183,21 +260,23 @@ export async function startBrowserSession(o) {
|
|
|
183
260
|
return;
|
|
184
261
|
}
|
|
185
262
|
clearTimeout(timeout);
|
|
186
|
-
const
|
|
187
|
-
if (pending?.timer === timer) {
|
|
188
|
-
pending = undefined;
|
|
189
|
-
armIdle();
|
|
190
|
-
}
|
|
191
|
-
reject(new Error('action expired'));
|
|
192
|
-
}, action.expiresAt - Date.now());
|
|
193
|
-
pending = {
|
|
263
|
+
const entry = {
|
|
194
264
|
action: JSON.parse(JSON.stringify(action)),
|
|
195
265
|
busy: false,
|
|
196
266
|
resolve: resolve,
|
|
197
267
|
reject,
|
|
198
268
|
verify: verify,
|
|
199
|
-
timer
|
|
269
|
+
timer: setTimeout(() => {
|
|
270
|
+
if (pending === entry) {
|
|
271
|
+
pending = undefined;
|
|
272
|
+
armIdle();
|
|
273
|
+
}
|
|
274
|
+
reject(entry.sending ? uncertain(entry) : expiredError(entry.failure));
|
|
275
|
+
}, action.expiresAt - Date.now()),
|
|
200
276
|
};
|
|
277
|
+
pending = entry;
|
|
278
|
+
advance(entry.action);
|
|
279
|
+
notify(waitingLine(action.expiresAt - Date.now()));
|
|
201
280
|
}),
|
|
202
281
|
};
|
|
203
282
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Buyer commands before a purchase exists: `verify` a file and `buy` it. */
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { inspect, inspectHeader, MAX_HEADER, parseHeader } from '../envelope/pay2.js';
|
|
5
5
|
import { AnnonError } from '../errors.js';
|
|
@@ -17,6 +17,7 @@ import { offer, resourceHeader, response } from './offers.js';
|
|
|
17
17
|
import { originFor } from './origin.js';
|
|
18
18
|
import { humanPaymentTerms, quotedSource, savedPayment, validatePaymentToken, } from './payment-terms.js';
|
|
19
19
|
import { claimPurchase } from './purchase-guard.js';
|
|
20
|
+
import { rememberPurchase, savedRecordIds } from './purchase-ref.js';
|
|
20
21
|
import { resourceAllowed } from './resources.js';
|
|
21
22
|
/** `verify`: a local file's header and body, or a remote file's header, against its listing. */
|
|
22
23
|
async function verifyFile(ctx) {
|
|
@@ -155,13 +156,10 @@ async function buy(ctx, preparedBuy) {
|
|
|
155
156
|
const guardDir = join(c.stateDir, 'purchase-guards');
|
|
156
157
|
await mkdir(guardDir, { recursive: true, mode: 0o700 });
|
|
157
158
|
const known = [];
|
|
158
|
-
for (const
|
|
159
|
-
if (
|
|
159
|
+
for (const id of await savedRecordIds(storeDir)) {
|
|
160
|
+
if (!isId(id))
|
|
160
161
|
continue;
|
|
161
|
-
const
|
|
162
|
-
if (!isId(envelope.id))
|
|
163
|
-
continue;
|
|
164
|
-
const saved = await sdk.store.load(purchaseKey(envelope.id));
|
|
162
|
+
const saved = await sdk.store.load(purchaseKey(id));
|
|
165
163
|
if (saved?.purchase.file_id === found.fileId &&
|
|
166
164
|
saved.purchase.contract === c.contractId &&
|
|
167
165
|
saved.terms.network === (saved.purchase.route === 'base-usdc' ? 'eip155:8453' : c.network))
|
|
@@ -176,6 +174,10 @@ async function buy(ctx, preparedBuy) {
|
|
|
176
174
|
return true;
|
|
177
175
|
if (!saved)
|
|
178
176
|
return Date.now() / 1000 < claimUntil;
|
|
177
|
+
// A NEAR send that may be on chain blocks another payment until reconciled or --force,
|
|
178
|
+
// even after the purchase expired: a late transfer still moves tokens.
|
|
179
|
+
if (saved.nearSubmission?.state === 'uncertain')
|
|
180
|
+
return true;
|
|
179
181
|
return (Number(saved.purchase.expires_at) > Date.now() / 1000 ||
|
|
180
182
|
(saved.purchase.route === 'base-usdc' && saved.attempts.length > 0));
|
|
181
183
|
},
|
|
@@ -212,6 +214,7 @@ async function buy(ctx, preparedBuy) {
|
|
|
212
214
|
if (renewalError)
|
|
213
215
|
throw renewalError;
|
|
214
216
|
await guard.saved(record.id);
|
|
217
|
+
await rememberPurchase(c, record.id);
|
|
215
218
|
},
|
|
216
219
|
recovery: buyer,
|
|
217
220
|
...(baseSponsorship ? { baseSponsorship } : {}),
|
|
@@ -277,11 +280,16 @@ async function buy(ctx, preparedBuy) {
|
|
|
277
280
|
: bought.instructions
|
|
278
281
|
? 'deposit_required'
|
|
279
282
|
: 'submitted',
|
|
280
|
-
|
|
283
|
+
// A browser send that failed after the wallet's send call has no hash to report.
|
|
284
|
+
...(bought.uncertain?.txHash ? { txHash: bought.uncertain.txHash } : {}),
|
|
281
285
|
backup: backupPath(c, bought.record.id),
|
|
282
286
|
...(bought.instructions ? { instructions: bought.instructions } : {}),
|
|
283
287
|
...(source ? { source } : {}),
|
|
284
|
-
next:
|
|
288
|
+
next: bought.uncertain
|
|
289
|
+
? `The wallet may have sent this payment. Do not pay again. Run annon status ${bought.record.id} until it is accepted.`
|
|
290
|
+
: bought.instructions
|
|
291
|
+
? 'Fund the deposit as instructed, then check status and open this purchase.'
|
|
292
|
+
: `Payment sent. Run annon status ${bought.record.id}, then annon open ${bought.record.id} --output <file>.`,
|
|
285
293
|
};
|
|
286
294
|
}
|
|
287
295
|
catch (error) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Input } from './cli.js';
|
|
2
|
+
export interface BuyFollow {
|
|
3
|
+
notify?: (message: string) => void;
|
|
4
|
+
/** Pause between status checks; resolves early when `signal` aborts. */
|
|
5
|
+
wait?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
6
|
+
/** Aborting stops the waiting only. The saved purchase is left as it is. */
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
/** Asked once the purchase is accepted and no `--open` path was given. */
|
|
9
|
+
savePath?: () => Promise<string | undefined>;
|
|
10
|
+
onWaiting?: (waiting: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
type Execute = (input: Input) => Promise<unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Runs one command. A `buy` is followed when the caller is a person (`follow`) or asked for
|
|
15
|
+
* `--open`; a deposit route returns its instructions without waiting.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buyFlow(input: Input, execute: Execute, follow: boolean, o?: BuyFollow): Promise<unknown>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { commonInput } from './cli-flags.js';
|
|
2
|
+
import { publicError } from './errors.js';
|
|
3
|
+
import { newOutput } from './io.js';
|
|
4
|
+
const CHECKS = 60;
|
|
5
|
+
const INTERVAL_MS = 3000;
|
|
6
|
+
const pause = (ms, signal) => new Promise((resolve) => {
|
|
7
|
+
const done = () => {
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
resolve();
|
|
10
|
+
};
|
|
11
|
+
const timer = setTimeout(done, ms);
|
|
12
|
+
signal?.addEventListener('abort', done, { once: true });
|
|
13
|
+
});
|
|
14
|
+
/** Status checks until the purchase is accepted, the checks run out or the wait is cancelled. */
|
|
15
|
+
async function accepted(execute, input, id, o) {
|
|
16
|
+
o.onWaiting?.(true);
|
|
17
|
+
try {
|
|
18
|
+
for (let check = 0; check < CHECKS && !o.signal?.aborted; check++) {
|
|
19
|
+
if (check > 0)
|
|
20
|
+
await (o.wait ?? pause)(INTERVAL_MS, o.signal);
|
|
21
|
+
if (o.signal?.aborted)
|
|
22
|
+
break;
|
|
23
|
+
const status = (await execute({
|
|
24
|
+
...commonInput(input),
|
|
25
|
+
command: 'status',
|
|
26
|
+
target: id,
|
|
27
|
+
}));
|
|
28
|
+
if (status.state === 'accepted')
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
o.onWaiting?.(false);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs one command. A `buy` is followed when the caller is a person (`follow`) or asked for
|
|
39
|
+
* `--open`; a deposit route returns its instructions without waiting.
|
|
40
|
+
*/
|
|
41
|
+
export async function buyFlow(input, execute, follow, o = {}) {
|
|
42
|
+
if (input.command !== 'buy' || (!follow && input.open === undefined))
|
|
43
|
+
return execute(input);
|
|
44
|
+
// Refuse a taken output name before any payment step.
|
|
45
|
+
if (input.open !== undefined)
|
|
46
|
+
await newOutput(input.open);
|
|
47
|
+
const bought = (await execute(input));
|
|
48
|
+
const id = bought.purchaseId;
|
|
49
|
+
if (typeof id !== 'string' || !['submitted', 'uncertain'].includes(String(bought.state)))
|
|
50
|
+
return bought;
|
|
51
|
+
o.notify?.(bought.state === 'uncertain'
|
|
52
|
+
? 'The wallet may have sent this payment. Do not pay again. Checking NEAR...'
|
|
53
|
+
: 'Payment sent. Waiting for NEAR to accept...');
|
|
54
|
+
// From here the payment exists: a later failure is reported next to the purchase, never instead.
|
|
55
|
+
const pending = (statusError) => ({
|
|
56
|
+
...bought,
|
|
57
|
+
settlement: 'pending',
|
|
58
|
+
...(statusError ? { statusError } : {}),
|
|
59
|
+
next: `Not accepted yet. Do not pay again. Run annon status ${id} until it is accepted, then annon open ${id} --output <file>.`,
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
if (!(await accepted(execute, input, id, o)))
|
|
63
|
+
return pending();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return pending(publicError(error));
|
|
67
|
+
}
|
|
68
|
+
const settled = { ...bought, state: 'accepted', next: `annon open ${id} --output <file>` };
|
|
69
|
+
const output = input.open ?? (await o.savePath?.());
|
|
70
|
+
if (!output)
|
|
71
|
+
return settled;
|
|
72
|
+
try {
|
|
73
|
+
const opened = (await execute({
|
|
74
|
+
...commonInput(input),
|
|
75
|
+
command: 'open',
|
|
76
|
+
target: id,
|
|
77
|
+
output,
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
...bought,
|
|
81
|
+
state: 'opened',
|
|
82
|
+
opened: { output: opened.output, bytes: opened.bytes },
|
|
83
|
+
next: 'The file is saved.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return { ...settled, openError: publicError(error) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The flag tables of the CLI: what every command accepts, for parsing, completion and follow-ups. */
|
|
2
|
+
import type { Command, Input } from './cli.js';
|
|
3
|
+
export declare const common: string[];
|
|
4
|
+
export declare const perCommand: Record<Command, string[]>;
|
|
5
|
+
export declare const camel: (flag: string) => string;
|
|
6
|
+
export declare const commandNames: Command[];
|
|
7
|
+
/** Every flag a command accepts, as typed on the command line. */
|
|
8
|
+
export declare const commandFlags: (command: Command) => string[];
|
|
9
|
+
/** The deployment, wallet and output options of one command, for a follow-up command it starts. */
|
|
10
|
+
export declare const commonInput: (input: Input) => Partial<Input>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const common = [
|
|
2
|
+
'no-open',
|
|
3
|
+
'json',
|
|
4
|
+
'config',
|
|
5
|
+
'contract-id',
|
|
6
|
+
'near-rpc',
|
|
7
|
+
'base-rpc',
|
|
8
|
+
'network',
|
|
9
|
+
'state-dir',
|
|
10
|
+
'relayer',
|
|
11
|
+
'trust-contract',
|
|
12
|
+
'wallet',
|
|
13
|
+
'evm-key-file',
|
|
14
|
+
'solana-key-file',
|
|
15
|
+
'near-key-file',
|
|
16
|
+
'near-account',
|
|
17
|
+
'near-public-key',
|
|
18
|
+
'backup-secret-file',
|
|
19
|
+
];
|
|
20
|
+
export const perCommand = {
|
|
21
|
+
shops: ['search', 'limit', 'offset'],
|
|
22
|
+
shop: ['id'],
|
|
23
|
+
catalog: ['shop', 'search', 'limit', 'offset'],
|
|
24
|
+
fund: [],
|
|
25
|
+
budget: [],
|
|
26
|
+
login: [],
|
|
27
|
+
init: [
|
|
28
|
+
'signer',
|
|
29
|
+
'credential-env',
|
|
30
|
+
'credential-keychain',
|
|
31
|
+
'agent',
|
|
32
|
+
'recovery',
|
|
33
|
+
'key-env',
|
|
34
|
+
'keychain-entry',
|
|
35
|
+
'new-key',
|
|
36
|
+
'daily-budget',
|
|
37
|
+
],
|
|
38
|
+
share: ['resume', 'price', 'shop', 'title', 'description', 'storage-budget', 'listing-storage'],
|
|
39
|
+
sell: ['resume', 'price', 'split', 'base', 'moderator', 'output', 'storage-budget'],
|
|
40
|
+
buy: [
|
|
41
|
+
'force',
|
|
42
|
+
'origin',
|
|
43
|
+
'route',
|
|
44
|
+
'from-chain',
|
|
45
|
+
'from-asset',
|
|
46
|
+
'refund-to',
|
|
47
|
+
'recovery',
|
|
48
|
+
'max-price',
|
|
49
|
+
'moderator',
|
|
50
|
+
'url',
|
|
51
|
+
'open',
|
|
52
|
+
'shop',
|
|
53
|
+
'search',
|
|
54
|
+
],
|
|
55
|
+
open: ['purchase', 'file', 'url', 'output'],
|
|
56
|
+
recover: ['recovery', 'purchase'],
|
|
57
|
+
stop: [],
|
|
58
|
+
verify: [],
|
|
59
|
+
status: [],
|
|
60
|
+
};
|
|
61
|
+
export const camel = (flag) => flag.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
62
|
+
export const commandNames = Object.keys(perCommand);
|
|
63
|
+
/** Every flag a command accepts, as typed on the command line. */
|
|
64
|
+
export const commandFlags = (command) => [...perCommand[command], ...common].map((flag) => `--${flag}`);
|
|
65
|
+
/** The deployment, wallet and output options of one command, for a follow-up command it starts. */
|
|
66
|
+
export const commonInput = (input) => Object.fromEntries(common
|
|
67
|
+
.map((flag) => [camel(flag), input[camel(flag)]])
|
|
68
|
+
.filter(([, v]) => v !== undefined));
|
package/dist/node/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BuyRoute } from '../routes.js';
|
|
2
|
+
import { type BuyFollow } from './buy-flow.js';
|
|
2
3
|
export type Command = 'shops' | 'shop' | 'catalog' | 'fund' | 'budget' | 'share' | 'sell' | 'buy' | 'open' | 'recover' | 'stop' | 'verify' | 'status' | 'login' | 'init';
|
|
3
4
|
export interface Input {
|
|
4
5
|
command: Command;
|
|
@@ -55,6 +56,13 @@ export interface Input {
|
|
|
55
56
|
file?: string;
|
|
56
57
|
url?: string;
|
|
57
58
|
output?: string;
|
|
59
|
+
/** `buy --open <path>`: wait for acceptance, then write the opened file there. */
|
|
60
|
+
open?: string;
|
|
58
61
|
}
|
|
59
62
|
export declare function parseCli(argv: string[]): Input;
|
|
60
|
-
|
|
63
|
+
/** How one command line is presented and followed up; the defaults are the script contract. */
|
|
64
|
+
export interface RunOptions extends BuyFollow {
|
|
65
|
+
/** Readable text in place of the JSON envelope. `--json` always wins. */
|
|
66
|
+
human?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare function runCli(argv: string[], execute: (input: Input) => Promise<unknown>, write: (text: string) => void, options?: RunOptions): Promise<number>;
|