@farmslot/adapter-web 0.2.0 → 0.3.1
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 +10 -0
- package/README.md +56 -3
- package/dist/browser-resolver.d.cts +1 -0
- package/dist/dapp/index.d.cts +32 -0
- package/dist/dapp/page-script.d.cts +96 -0
- package/dist/dapp/signature-log.d.cts +119 -0
- package/dist/dapp/strict-wallet.d.cts +27 -0
- package/dist/dapp/wallet-requests.d.cts +48 -0
- package/dist/origin.d.cts +21 -0
- package/package.json +9 -1
- package/src/browser-resolver.cjs +95 -21
- package/src/dapp/index.cjs +75 -0
- package/src/dapp/page-script.cjs +400 -0
- package/src/dapp/signature-log.cjs +314 -0
- package/src/dapp/strict-wallet.cjs +169 -0
- package/src/dapp/wallet-requests.cjs +321 -0
- package/src/origin.cjs +53 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ All notable changes to `@farmslot/adapter-web` are tracked here.
|
|
|
6
6
|
|
|
7
7
|
- Active-development baseline; add user-facing changes here before release or package publication.
|
|
8
8
|
|
|
9
|
+
## 0.3.1 - 2026-10-06
|
|
10
|
+
|
|
11
|
+
- Browser probes no longer leave Chrome for Testing running when the resolver is stopped mid-probe (a hook timeout, a signal, or the resolver killed outright): the probe browser and its temporary profile are removed.
|
|
12
|
+
- A resolver killed outright cannot clean up; the next probe by the same user stops its browser and removes its profile. `browser-resolver` exports that step as `reapOrphanedProbes(tmp?)`.
|
|
13
|
+
|
|
14
|
+
## 0.3.0 - 2026-10-05
|
|
15
|
+
|
|
16
|
+
- Add `dapp`, the web3 layer for a dapp under test, moved from `@deeeed/metamask-harness`'s Web Terminal adapter: the strict EIP-1193 test wallet (`createStrictWallet`), the page script that wraps the app's provider and logs its wallet requests or injects a provider the host answers (`pageScriptSource`, `pageReadyExpression`), the host's side of its bindings (`createWalletRequestBinding`: frame and document attribution, `outside-app-frame` and `unattributed` records, refusals) and the wallet request log (`windowSinceCursor`, `resetWindow`, `evaluateSignatureLog`, `awaitSignatureLog`, `writeLogArtifact`). Product policy is passed in: the typed data to refuse (`refuseTypedData`), the injected wallet's EIP-6963 identity and the log entries and typed-data classes a product forbids. Page bindings are `__farmslotWallet{Log,Request,Resolve}` and the page marker `__farmslotDapp`.
|
|
17
|
+
- Add `origin` (`isAppUrl`, `originOf`, `isAppTopFrameContext`, `shortUrl`): exact app-origin checks, moved from the same adapter.
|
|
18
|
+
|
|
9
19
|
## 0.2.0 - 2026-10-05
|
|
10
20
|
|
|
11
21
|
- Add `launch-browser` (`launchBrowser`: launch or release one isolated, detached, owned Chromium with an unpacked extension; `homeTabsToClose`) and `slot-title` (stamp the farm slot id into the extension home tab's title), moved from `@deeeed/metamask-harness`. Product knowledge is now passed in: the home page, its default title, the owner-root rule, a lock held for the launch, and the rerun command named in hints. `slot-title`'s `applyPersistentSlotTitle` takes `{ slotId, defaultTitle }`, `buildStampExpression(slotId, defaultTitle)` and `stampHomeTabsViaCdp({ homePage, defaultTitle, ... })` match it, and CDP stamping uses the global `WebSocket` (else `ws`), loaded only when it stamps.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @farmslot/adapter-web
|
|
2
2
|
|
|
3
|
-
Web platform pieces for Farmslot recipes: pick and launch an isolated Chromium, prove which process owns a CDP port, load an unpacked extension,
|
|
3
|
+
Web platform pieces for Farmslot recipes: pick and launch an isolated Chromium, prove which process owns a CDP port, load an unpacked extension, give macOS focus back after a headed launch, and record (or answer) a dapp's wallet requests. A harness builds its `web` platform on these; the modules hold no product knowledge.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
const { resolveBrowser } = require('@farmslot/adapter-web/browser-resolver');
|
|
@@ -17,16 +17,18 @@ Docs: https://farmslot.io/docs/reference/adapter-web
|
|
|
17
17
|
| `browser-resolver` | Chrome for Testing vs branded Chrome, probe-launch and its cache, extension loading method; CLI entry |
|
|
18
18
|
| `browser-cdp` | browser-level CDP client with deadlines, CDP-port ownership proof, `Extensions.loadUnpacked`, window placement |
|
|
19
19
|
| `chrome-args` | remote-debugging flags, isolated profile flags, runtime identity nonce, launch quarantine markers |
|
|
20
|
+
| `dapp` | web dapps: strict EIP-1193 test wallet, wallet-request page script and its host binding, wallet request log |
|
|
20
21
|
| `extension-id` | Chromium extension id from a manifest key or an unpacked directory |
|
|
21
22
|
| `launch-browser` | launch (or release) one isolated, detached, owned Chromium with an unpacked extension |
|
|
22
23
|
| `macos-focus` | capture the frontmost app before a headed launch and restore it if one of our browsers took it |
|
|
24
|
+
| `origin` | exact app-origin checks, the app top-frame CDP context check, URLs without query strings |
|
|
23
25
|
| `page-target` | pick the page on an origin, preferring one whose URL carries a hash |
|
|
24
26
|
| `playwright-cdp` | page evaluation over a raw CDP session (safe under LavaMoat scuttling) |
|
|
25
27
|
| `slot-title` | prefix the extension home tab's title with the farm slot id, kept across the page's own title resets |
|
|
26
28
|
| `validation-process-ownership` | find and stop the processes that own a slot profile |
|
|
27
29
|
| `validation-launch-supervisor` | supervised child for one validation launch: port lease, quarantine and cleanup |
|
|
28
30
|
|
|
29
|
-
Sources are CommonJS (`src
|
|
31
|
+
Sources are CommonJS (`src/**/*.cjs`) and ship as is; `yarn build` emits `.d.cts` declarations. Every library subpath can be `require`d or `import`ed with named imports, except `validation-launch-supervisor`: it is a child-process entry (it runs when loaded), so hosts `require.resolve` it and fork it.
|
|
30
32
|
|
|
31
33
|
## Launching a browser
|
|
32
34
|
|
|
@@ -52,6 +54,57 @@ launchBrowser({
|
|
|
52
54
|
});
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
## Web dapps
|
|
58
|
+
|
|
59
|
+
`dapp` is the web3 layer for a dapp under test: the browser's wallet requests are recorded and, with the injected signer, answered by a strict test wallet. The wallet host (a process attached to the slot browser over CDP) puts three pieces together:
|
|
60
|
+
|
|
61
|
+
1. `pageScriptSource({ signer, appOrigin, refuseTypedData, injectedWallet })` is the preload to register with `Page.addScriptToEvaluateOnNewDocument`. It runs only in the app's top frame. With `signer: 'extension'` it wraps the provider a wallet extension injects; with `'injected'` it provides an EIP-1193 provider, announced over EIP-6963 with `injectedWallet.info`, whose requests go to the host. Every request in `LOGGED_METHODS` is reported through `LOG_BINDING` (method, EIP-712 primary type, domain and active chain, outcome, an error code and fixed category; never params or signatures). A request with no host binding attached is refused with 4100.
|
|
62
|
+
2. `createWalletRequestBinding({ client, appOrigin, signer, wallet, refuseTypedData, record, say })` is the host's side. It judges each binding call against the frame and document that made it: an iframe, a blank popup or a foreign page is refused and recorded as `outside-app-frame`, and a call it can't attribute within `PENDING_CALL_TTL_MS` is recorded as `unattributed`. The host calls `install` when it hooks a tab, `commit` when the tab's top frame commits a document, `drain` once the tab's domains are enabled and `detach` when it goes.
|
|
63
|
+
3. `createStrictWallet({ account, chainId })` answers the injected requests. It signs with the account the host passes and refuses what MetaMask refuses (typed data for another chain, another account), plus transactions; reads go to a public RPC.
|
|
64
|
+
|
|
65
|
+
`pageReadyExpression({ signer, refusesTypedData })` checks a committed document: the bindings are attached, the preload installed with this policy, and the provider the app sees wrapped.
|
|
66
|
+
|
|
67
|
+
Product policy is passed in. `refuseTypedData` is `{ reason, kind, message }`: typed data that `reason(typedData)` flags never reaches the signer. It is logged as `kind` and answered with code 4100 and `message`. `reason` also runs in the page, so it must be self-contained.
|
|
68
|
+
|
|
69
|
+
The log is read through `{ logFile, cursorFile }`:
|
|
70
|
+
|
|
71
|
+
- `windowSinceCursor` and `resetWindow` read the current window and start a new one.
|
|
72
|
+
- `evaluateSignatureLog(entries, node, policy)` and `awaitSignatureLog` (which retries until `node.timeout_ms`) check `allowed_primary_types`, `counts`, `request_counts`, `methods`, `max_typed_data_requests`, `expect_confirmations`, `require_active_chain` and `max_rejected`.
|
|
73
|
+
- `writeLogArtifact` keeps a window as run evidence.
|
|
74
|
+
|
|
75
|
+
`policy.typedDataClasses` adds limited counters (for example, session-key payloads at most `node.max_session_requests`, default 0); `policy.forbiddenEntries` adds entries that always fail.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const {
|
|
79
|
+
createStrictWallet,
|
|
80
|
+
createWalletRequestBinding,
|
|
81
|
+
pageScriptSource,
|
|
82
|
+
} = require('@farmslot/adapter-web/dapp');
|
|
83
|
+
|
|
84
|
+
const refuseTypedData = {
|
|
85
|
+
reason: function productionReason(data) {
|
|
86
|
+
return data?.message?.env === 'production' ? 'env=production' : null;
|
|
87
|
+
},
|
|
88
|
+
kind: 'refused-production',
|
|
89
|
+
message: 'Refused: this run signs only for staging.',
|
|
90
|
+
};
|
|
91
|
+
const source = pageScriptSource({
|
|
92
|
+
signer: 'injected',
|
|
93
|
+
appOrigin,
|
|
94
|
+
refuseTypedData,
|
|
95
|
+
injectedWallet: { info: { uuid, name: 'Test wallet', icon, rdns: 'test.wallet' } },
|
|
96
|
+
});
|
|
97
|
+
const wallet = createStrictWallet({ account, chainId: 1 });
|
|
98
|
+
const binding = createWalletRequestBinding({
|
|
99
|
+
client,
|
|
100
|
+
appOrigin,
|
|
101
|
+
signer: 'injected',
|
|
102
|
+
wallet,
|
|
103
|
+
refuseTypedData,
|
|
104
|
+
record: (entry) => appendLine(logFile, entry),
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
55
108
|
## Resolver CLI
|
|
56
109
|
|
|
57
110
|
`browser-resolver.cjs` is also a command, for hosts that need a bounded child process (a hung CDP call can't stall the caller). Every command finishes within 120 s, exits 0 on success and 1 with the error message on stderr otherwise. Hosts can keep their own script path by calling `runCli(argv)`.
|
|
@@ -64,7 +117,7 @@ launchBrowser({
|
|
|
64
117
|
|
|
65
118
|
## Maintenance rules
|
|
66
119
|
|
|
67
|
-
1. No product knowledge. A product's build locks, titles, fixtures
|
|
120
|
+
1. No product knowledge. A product's build locks, titles, fixtures, wallet state and signing policy stay in its harness and reach these modules as arguments.
|
|
68
121
|
2. Process and on-disk identity strings (runtime nonce flag, launch markers, probe cache path) are how a launched browser and its profile are recognised. Changing one makes browsers started under the old name unrecognised; call that out in the release notes.
|
|
69
122
|
3. Every CDP call has a deadline. Never add an unbounded wait.
|
|
70
123
|
4. Never steal focus. A headed launch restores the previous frontmost app; window activation goes by pid, not by app name. `placeWindow` moves a visible window without activating it (restoring a minimized one can).
|
|
@@ -64,6 +64,7 @@ export function probeLaunch(executable: any, { launchMethod, display, settleMs,
|
|
|
64
64
|
durationMs: number;
|
|
65
65
|
}>;
|
|
66
66
|
export function readProbeCache(cachePath: any): any;
|
|
67
|
+
export function reapOrphanedProbes(tmp?: string): void;
|
|
67
68
|
export function renderCheck(port: any): Promise<{
|
|
68
69
|
ok: boolean;
|
|
69
70
|
reason?: undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type TypedDataRefusal = import("./page-script.cjs").TypedDataRefusal;
|
|
2
|
+
export type InjectedWallet = import("./page-script.cjs").InjectedWallet;
|
|
3
|
+
export type SignatureLog = import("./signature-log.cjs").SignatureLog;
|
|
4
|
+
export type SignaturePolicy = import("./signature-log.cjs").SignaturePolicy;
|
|
5
|
+
export type SignatureSummary = import("./signature-log.cjs").SignatureSummary;
|
|
6
|
+
export type LogEntry = import("./signature-log.cjs").LogEntry;
|
|
7
|
+
import { DEFAULT_KNOWN_CHAINS } from "./strict-wallet.cjs";
|
|
8
|
+
import { ERROR_CATEGORIES } from "./page-script.cjs";
|
|
9
|
+
import { LOGGED_METHODS } from "./page-script.cjs";
|
|
10
|
+
import { LOG_BINDING } from "./page-script.cjs";
|
|
11
|
+
import { PAGE_MARKER } from "./page-script.cjs";
|
|
12
|
+
import { PENDING_CALL_TTL_MS } from "./wallet-requests.cjs";
|
|
13
|
+
import { READ_ONLY_RPC_METHODS } from "./strict-wallet.cjs";
|
|
14
|
+
import { REQUEST_BINDING } from "./page-script.cjs";
|
|
15
|
+
import { RESOLVE_FN } from "./page-script.cjs";
|
|
16
|
+
import { awaitSignatureLog } from "./signature-log.cjs";
|
|
17
|
+
import { createStrictWallet } from "./strict-wallet.cjs";
|
|
18
|
+
import { createWalletRequestBinding } from "./wallet-requests.cjs";
|
|
19
|
+
import { evaluateSignatureLog } from "./signature-log.cjs";
|
|
20
|
+
import { isTypedDataRequest } from "./signature-log.cjs";
|
|
21
|
+
import { pageReadyExpression } from "./page-script.cjs";
|
|
22
|
+
import { pageScriptSource } from "./page-script.cjs";
|
|
23
|
+
import { parseLog } from "./signature-log.cjs";
|
|
24
|
+
import { readCursor } from "./signature-log.cjs";
|
|
25
|
+
import { readLog } from "./signature-log.cjs";
|
|
26
|
+
import { resetWindow } from "./signature-log.cjs";
|
|
27
|
+
import { shortPrimaryType } from "./signature-log.cjs";
|
|
28
|
+
import { summarize } from "./signature-log.cjs";
|
|
29
|
+
import { windowSinceCursor } from "./signature-log.cjs";
|
|
30
|
+
import { writeCursor } from "./signature-log.cjs";
|
|
31
|
+
import { writeLogArtifact } from "./signature-log.cjs";
|
|
32
|
+
export { DEFAULT_KNOWN_CHAINS, ERROR_CATEGORIES, LOGGED_METHODS, LOG_BINDING, PAGE_MARKER, PENDING_CALL_TTL_MS, READ_ONLY_RPC_METHODS, REQUEST_BINDING, RESOLVE_FN, awaitSignatureLog, createStrictWallet, createWalletRequestBinding, evaluateSignatureLog, isTypedDataRequest, pageReadyExpression, pageScriptSource, parseLog, readCursor, readLog, resetWindow, shortPrimaryType, summarize, windowSinceCursor, writeCursor, writeLogArtifact };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export type TypedDataRefusal = {
|
|
2
|
+
/**
|
|
3
|
+
* Why the run must not sign this typed data, or null. Runs in the page too: self-contained (no closure, no imports).
|
|
4
|
+
*/
|
|
5
|
+
reason: (typedData: any) => string | null;
|
|
6
|
+
/**
|
|
7
|
+
* The log entry kind of a refusal.
|
|
8
|
+
*/
|
|
9
|
+
kind: string;
|
|
10
|
+
/**
|
|
11
|
+
* The refusal's error message (code 4100).
|
|
12
|
+
*/
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
export type InjectedWallet = {
|
|
16
|
+
/**
|
|
17
|
+
* EIP-6963 provider info to announce.
|
|
18
|
+
*/
|
|
19
|
+
info: {
|
|
20
|
+
uuid: string;
|
|
21
|
+
name: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
rdns: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Present the provider as MetaMask (`isMetaMask`, `_metamask.isUnlocked`).
|
|
27
|
+
*/
|
|
28
|
+
isMetaMask?: boolean | undefined;
|
|
29
|
+
};
|
|
30
|
+
export const ERROR_CATEGORIES: Readonly<{
|
|
31
|
+
4001: "user-rejected";
|
|
32
|
+
4100: "unauthorized";
|
|
33
|
+
4200: "unsupported-method";
|
|
34
|
+
4900: "disconnected";
|
|
35
|
+
4901: "chain-disconnected";
|
|
36
|
+
4902: "unrecognized-chain";
|
|
37
|
+
[-32602]: "invalid-params";
|
|
38
|
+
[-32603]: "internal";
|
|
39
|
+
}>;
|
|
40
|
+
export const LOGGED_METHODS: readonly string[];
|
|
41
|
+
export const LOG_BINDING: "__farmslotWalletLog";
|
|
42
|
+
export const PAGE_MARKER: "__farmslotDapp";
|
|
43
|
+
export const REQUEST_BINDING: "__farmslotWalletRequest";
|
|
44
|
+
export const REFUSAL_CHECK_FAILED: "the refusal check threw";
|
|
45
|
+
export const RESOLVE_FN: "__farmslotWalletResolve";
|
|
46
|
+
/**
|
|
47
|
+
* Refuse a policy the page or the host could not apply: kind and message must
|
|
48
|
+
* be non-empty strings, and reason a function whose source is an expression
|
|
49
|
+
* in the page (a function declaration or an arrow; not a method shorthand, a
|
|
50
|
+
* bound or a native function).
|
|
51
|
+
* @param {TypedDataRefusal | null | undefined} refuseTypedData
|
|
52
|
+
*/
|
|
53
|
+
export function assertTypedDataRefusal(refuseTypedData: TypedDataRefusal | null | undefined): void;
|
|
54
|
+
/**
|
|
55
|
+
* An expression that is true on a document whose preload is installed for
|
|
56
|
+
* this signer and policy, whose bindings are attached, and whose provider the
|
|
57
|
+
* app sees (window.ethereum) is wrapped with no wrap failure.
|
|
58
|
+
*
|
|
59
|
+
* @param {{ signer: 'extension' | 'injected', refusesTypedData: boolean }} options
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
export function pageReadyExpression({ signer, refusesTypedData }: {
|
|
63
|
+
signer: "extension" | "injected";
|
|
64
|
+
refusesTypedData: boolean;
|
|
65
|
+
}): string;
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {object} TypedDataRefusal
|
|
68
|
+
* @property {(typedData: any) => string | null} reason Why the run must not sign this typed data, or null. Runs in the page too: self-contained (no closure, no imports).
|
|
69
|
+
* @property {string} kind The log entry kind of a refusal.
|
|
70
|
+
* @property {string} message The refusal's error message (code 4100).
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {object} InjectedWallet
|
|
74
|
+
* @property {{ uuid: string, name: string, icon: string, rdns: string }} info EIP-6963 provider info to announce.
|
|
75
|
+
* @property {boolean} [isMetaMask] Present the provider as MetaMask (`isMetaMask`, `_metamask.isUnlocked`).
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* The preload for one app origin.
|
|
79
|
+
* - `signer`: 'extension' wraps the provider a wallet extension injects;
|
|
80
|
+
* 'injected' provides one whose requests go to the host (REQUEST_BINDING).
|
|
81
|
+
* - `refuseTypedData`: `{ reason, kind, message }` or null. `reason(typedData)`
|
|
82
|
+
* returns why the run must not sign it, or null; it runs in the page, so it
|
|
83
|
+
* must be a self-contained function (no closure, no imports). A refusal is
|
|
84
|
+
* logged as `kind` and answered with code 4100 and `message`.
|
|
85
|
+
* - `injectedWallet` (injected signer): `{ info, isMetaMask }`, the EIP-6963
|
|
86
|
+
* provider info to announce and whether the provider presents as MetaMask.
|
|
87
|
+
*
|
|
88
|
+
* @param {{ signer: 'extension' | 'injected', appOrigin: string, refuseTypedData?: TypedDataRefusal | null, injectedWallet?: InjectedWallet }} options
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
export function pageScriptSource({ signer, appOrigin, refuseTypedData, injectedWallet }: {
|
|
92
|
+
signer: "extension" | "injected";
|
|
93
|
+
appOrigin: string;
|
|
94
|
+
refuseTypedData?: TypedDataRefusal | null;
|
|
95
|
+
injectedWallet?: InjectedWallet;
|
|
96
|
+
}): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export type SignatureLog = {
|
|
2
|
+
logFile: string;
|
|
3
|
+
cursorFile: string;
|
|
4
|
+
};
|
|
5
|
+
export type LogEntry = {
|
|
6
|
+
seq: number;
|
|
7
|
+
kind?: string;
|
|
8
|
+
[field: string]: any;
|
|
9
|
+
};
|
|
10
|
+
export type PrimaryTypeSummary = {
|
|
11
|
+
requested: number;
|
|
12
|
+
signed: number;
|
|
13
|
+
rejected: number;
|
|
14
|
+
chainIds: Array<number | null | undefined>;
|
|
15
|
+
};
|
|
16
|
+
export type SignatureSummary = {
|
|
17
|
+
requests: number;
|
|
18
|
+
typedDataRequests: number;
|
|
19
|
+
confirmationsShown: number;
|
|
20
|
+
unattributed: number;
|
|
21
|
+
outsideAppFrame: number;
|
|
22
|
+
byPrimaryType: Record<string, PrimaryTypeSummary>;
|
|
23
|
+
byMethod: Record<string, number>;
|
|
24
|
+
};
|
|
25
|
+
export type SignaturePolicy = {
|
|
26
|
+
typedDataClasses?: Record<string, {
|
|
27
|
+
match(entry: LogEntry): boolean;
|
|
28
|
+
param: string;
|
|
29
|
+
defaultMax: number;
|
|
30
|
+
failure(count: number, max: number): string;
|
|
31
|
+
}> | undefined;
|
|
32
|
+
forbiddenEntries?: Record<string, {
|
|
33
|
+
match(entry: LogEntry): boolean;
|
|
34
|
+
failure(count: number): string;
|
|
35
|
+
}> | undefined;
|
|
36
|
+
};
|
|
37
|
+
/** @param {SignatureLog} log @param {Record<string, any>} [node] @param {SignaturePolicy} [policy] */
|
|
38
|
+
export function awaitSignatureLog(log: SignatureLog, node?: Record<string, any>, policy?: SignaturePolicy): Promise<{
|
|
39
|
+
window: {
|
|
40
|
+
cursor: number;
|
|
41
|
+
entries: LogEntry[];
|
|
42
|
+
total: number;
|
|
43
|
+
};
|
|
44
|
+
result: {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
failures: string[];
|
|
47
|
+
summary: SignatureSummary & Record<string, any>;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* @param {LogEntry[]} entries
|
|
52
|
+
* @param {Record<string, any>} [node]
|
|
53
|
+
* @param {SignaturePolicy} [policy]
|
|
54
|
+
* @returns {{ ok: boolean, failures: string[], summary: SignatureSummary & Record<string, any> }}
|
|
55
|
+
*/
|
|
56
|
+
export function evaluateSignatureLog(entries: LogEntry[], node?: Record<string, any>, policy?: SignaturePolicy): {
|
|
57
|
+
ok: boolean;
|
|
58
|
+
failures: string[];
|
|
59
|
+
summary: SignatureSummary & Record<string, any>;
|
|
60
|
+
};
|
|
61
|
+
/** @param {LogEntry} entry */
|
|
62
|
+
export function isTypedDataRequest(entry: LogEntry): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {{ logFile: string, cursorFile: string }} SignatureLog
|
|
65
|
+
* @typedef {{ seq: number, kind?: string, [field: string]: any }} LogEntry
|
|
66
|
+
* @typedef {{ requested: number, signed: number, rejected: number, chainIds: Array<number | null | undefined> }} PrimaryTypeSummary
|
|
67
|
+
* @typedef {object} SignatureSummary
|
|
68
|
+
* @property {number} requests
|
|
69
|
+
* @property {number} typedDataRequests
|
|
70
|
+
* @property {number} confirmationsShown
|
|
71
|
+
* @property {number} unattributed
|
|
72
|
+
* @property {number} outsideAppFrame
|
|
73
|
+
* @property {Record<string, PrimaryTypeSummary>} byPrimaryType
|
|
74
|
+
* @property {Record<string, number>} byMethod
|
|
75
|
+
* @typedef {object} SignaturePolicy
|
|
76
|
+
* @property {Record<string, { match(entry: LogEntry): boolean, param: string, defaultMax: number, failure(count: number, max: number): string }>} [typedDataClasses]
|
|
77
|
+
* @property {Record<string, { match(entry: LogEntry): boolean, failure(count: number): string }>} [forbiddenEntries]
|
|
78
|
+
*/
|
|
79
|
+
/** @param {string} text @returns {LogEntry[]} */
|
|
80
|
+
export function parseLog(text: string): LogEntry[];
|
|
81
|
+
/** @param {SignatureLog} log @returns {Promise<number>} */
|
|
82
|
+
export function readCursor({ cursorFile }: SignatureLog): Promise<number>;
|
|
83
|
+
/** @param {SignatureLog} log @returns {Promise<LogEntry[]>} */
|
|
84
|
+
export function readLog({ logFile }: SignatureLog): Promise<LogEntry[]>;
|
|
85
|
+
/** @param {SignatureLog} log */
|
|
86
|
+
export function resetWindow(log: SignatureLog): Promise<{
|
|
87
|
+
cursor: number;
|
|
88
|
+
previousEntries: number;
|
|
89
|
+
}>;
|
|
90
|
+
/** @param {unknown} primaryType @returns {string | null} */
|
|
91
|
+
export function shortPrimaryType(primaryType: unknown): string | null;
|
|
92
|
+
/**
|
|
93
|
+
* @param {LogEntry[]} entries
|
|
94
|
+
* @param {SignaturePolicy} [policy]
|
|
95
|
+
* @returns {SignatureSummary & Record<string, any>} The policy's counters are extra top-level keys.
|
|
96
|
+
*/
|
|
97
|
+
export function summarize(entries: LogEntry[], policy?: SignaturePolicy): SignatureSummary & Record<string, any>;
|
|
98
|
+
/** @param {SignatureLog} log */
|
|
99
|
+
export function windowSinceCursor(log: SignatureLog): Promise<{
|
|
100
|
+
cursor: number;
|
|
101
|
+
entries: LogEntry[];
|
|
102
|
+
total: number;
|
|
103
|
+
}>;
|
|
104
|
+
/** @param {SignatureLog} log @param {number} seq */
|
|
105
|
+
export function writeCursor({ cursorFile }: SignatureLog, seq: number): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* @param {{ artifactsDir?: string, nodeId: string, folder: string }} target
|
|
108
|
+
* @param {unknown} payload
|
|
109
|
+
* @returns {Promise<Array<{ path: string, type: 'report', nodeId: string }>>}
|
|
110
|
+
*/
|
|
111
|
+
export function writeLogArtifact({ artifactsDir, nodeId, folder }: {
|
|
112
|
+
artifactsDir?: string;
|
|
113
|
+
nodeId: string;
|
|
114
|
+
folder: string;
|
|
115
|
+
}, payload: unknown): Promise<Array<{
|
|
116
|
+
path: string;
|
|
117
|
+
type: "report";
|
|
118
|
+
nodeId: string;
|
|
119
|
+
}>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const DEFAULT_KNOWN_CHAINS: readonly number[];
|
|
2
|
+
export const READ_ONLY_RPC_METHODS: readonly string[];
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} options
|
|
5
|
+
* @param {{ address: string, signTypedData(args: any): Promise<string>, signMessage(args: any): Promise<string> }} options.account
|
|
6
|
+
* @param {number} [options.chainId]
|
|
7
|
+
* @param {readonly number[]} [options.knownChains]
|
|
8
|
+
* @param {boolean} [options.refuseSwitch]
|
|
9
|
+
* @param {typeof fetch} [options.fetchImpl]
|
|
10
|
+
*/
|
|
11
|
+
export function createStrictWallet({ account, chainId, knownChains, refuseSwitch, fetchImpl, }: {
|
|
12
|
+
account: {
|
|
13
|
+
address: string;
|
|
14
|
+
signTypedData(args: any): Promise<string>;
|
|
15
|
+
signMessage(args: any): Promise<string>;
|
|
16
|
+
};
|
|
17
|
+
chainId?: number | undefined;
|
|
18
|
+
knownChains?: readonly number[] | undefined;
|
|
19
|
+
refuseSwitch?: boolean | undefined;
|
|
20
|
+
fetchImpl?: typeof fetch | undefined;
|
|
21
|
+
}): {
|
|
22
|
+
request: ({ method, params }: {
|
|
23
|
+
method: string;
|
|
24
|
+
params?: any[];
|
|
25
|
+
}) => Promise<any>;
|
|
26
|
+
readonly chainId: number;
|
|
27
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const PENDING_CALL_TTL_MS: 5000;
|
|
2
|
+
/**
|
|
3
|
+
* - `client`: `{ send(method, params, sessionId), on(event, handler(params, sessionId)) }`.
|
|
4
|
+
* - `signer`: 'extension' (log only) or 'injected' (`wallet` answers requests).
|
|
5
|
+
* - `wallet`: an EIP-1193 `{ request }` for the injected signer.
|
|
6
|
+
* - `refuseTypedData`: the page script's policy (`{ reason, kind, message }`),
|
|
7
|
+
* applied again here before the wallet signs.
|
|
8
|
+
* - `record(entry)`: append one entry to the wallet request log.
|
|
9
|
+
* - `say(message)`: the host's own log.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} options
|
|
12
|
+
* @param {{ send(method: string, params?: object, sessionId?: string): Promise<any>, on(method: string, handler: (params: any, sessionId?: string) => void): unknown }} options.client
|
|
13
|
+
* @param {string} options.appOrigin
|
|
14
|
+
* @param {'extension' | 'injected'} options.signer
|
|
15
|
+
* @param {{ request(args: { method: string, params?: unknown }): Promise<unknown> } | null} [options.wallet]
|
|
16
|
+
* @param {import('./page-script.cjs').TypedDataRefusal | null} [options.refuseTypedData]
|
|
17
|
+
* @param {(entry: Record<string, unknown>) => void} options.record
|
|
18
|
+
* @param {(message: string) => void} [options.say]
|
|
19
|
+
*/
|
|
20
|
+
export function createWalletRequestBinding({ client, appOrigin, signer, wallet, refuseTypedData, record, say, }: {
|
|
21
|
+
client: {
|
|
22
|
+
send(method: string, params?: object, sessionId?: string): Promise<any>;
|
|
23
|
+
on(method: string, handler: (params: any, sessionId?: string) => void): unknown;
|
|
24
|
+
};
|
|
25
|
+
appOrigin: string;
|
|
26
|
+
signer: "extension" | "injected";
|
|
27
|
+
wallet?: {
|
|
28
|
+
request(args: {
|
|
29
|
+
method: string;
|
|
30
|
+
params?: unknown;
|
|
31
|
+
}): Promise<unknown>;
|
|
32
|
+
} | null | undefined;
|
|
33
|
+
refuseTypedData?: import("./page-script.cjs").TypedDataRefusal | null | undefined;
|
|
34
|
+
record: (entry: Record<string, unknown>) => void;
|
|
35
|
+
say?: ((message: string) => void) | undefined;
|
|
36
|
+
}): {
|
|
37
|
+
/**
|
|
38
|
+
* Track an app tab and add its bindings. Call before the page script is
|
|
39
|
+
* registered; neither needs the target running.
|
|
40
|
+
*/
|
|
41
|
+
install(sessionId: any, targetId: any): Promise<[any, ...any[]]>;
|
|
42
|
+
/** The tab's top frame committed `url` (a frameNavigated or the frame tree). */
|
|
43
|
+
commit(sessionId: any, url: any, loaderId: any): void;
|
|
44
|
+
/** Judge the calls that were waiting for the tab's contexts. */
|
|
45
|
+
drain: (sessionId: any) => void;
|
|
46
|
+
/** The tab detached: calls still waiting for their document can no longer be judged. */
|
|
47
|
+
detach(sessionId: any): void;
|
|
48
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{ origin?: string, auxData?: { isDefault?: boolean, frameId?: string } } | null | undefined} context
|
|
3
|
+
* @param {{ targetId: string | undefined, appOrigin: string, committedUrl: string | null }} scope
|
|
4
|
+
*/
|
|
5
|
+
export function isAppTopFrameContext(context: {
|
|
6
|
+
origin?: string;
|
|
7
|
+
auxData?: {
|
|
8
|
+
isDefault?: boolean;
|
|
9
|
+
frameId?: string;
|
|
10
|
+
};
|
|
11
|
+
} | null | undefined, { targetId, appOrigin, committedUrl }: {
|
|
12
|
+
targetId: string | undefined;
|
|
13
|
+
appOrigin: string;
|
|
14
|
+
committedUrl: string | null;
|
|
15
|
+
}): boolean;
|
|
16
|
+
/** @param {unknown} url @param {string} appOrigin */
|
|
17
|
+
export function isAppUrl(url: unknown, appOrigin: string): boolean;
|
|
18
|
+
/** @param {unknown} url @returns {string | null} */
|
|
19
|
+
export function originOf(url: unknown): string | null;
|
|
20
|
+
/** @param {unknown} url @returns {string} */
|
|
21
|
+
export function shortUrl(url: unknown): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farmslot/adapter-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Web platform adapter for Farmslot recipes: resolve and launch an isolated Chromium, own its CDP port, load unpacked extensions, and keep macOS focus.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"exports": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/chrome-args.d.cts",
|
|
17
17
|
"default": "./src/chrome-args.cjs"
|
|
18
18
|
},
|
|
19
|
+
"./dapp": {
|
|
20
|
+
"types": "./dist/dapp/index.d.cts",
|
|
21
|
+
"default": "./src/dapp/index.cjs"
|
|
22
|
+
},
|
|
19
23
|
"./extension-id": {
|
|
20
24
|
"types": "./dist/extension-id.d.cts",
|
|
21
25
|
"default": "./src/extension-id.cjs"
|
|
@@ -28,6 +32,10 @@
|
|
|
28
32
|
"types": "./dist/macos-focus.d.cts",
|
|
29
33
|
"default": "./src/macos-focus.cjs"
|
|
30
34
|
},
|
|
35
|
+
"./origin": {
|
|
36
|
+
"types": "./dist/origin.d.cts",
|
|
37
|
+
"default": "./src/origin.cjs"
|
|
38
|
+
},
|
|
31
39
|
"./package.json": "./package.json",
|
|
32
40
|
"./page-target": {
|
|
33
41
|
"types": "./dist/page-target.d.cts",
|