@farmslot/adapter-web 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -6,6 +6,11 @@ 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.0 - 2026-10-05
10
+
11
+ - 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`.
12
+ - Add `origin` (`isAppUrl`, `originOf`, `isAppTopFrameContext`, `shortUrl`): exact app-origin checks, moved from the same adapter.
13
+
9
14
  ## 0.2.0 - 2026-10-05
10
15
 
11
16
  - 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, and give macOS focus back after a headed launch. A harness builds its `web` platform on these; the modules hold no product knowledge.
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/*.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.
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 and wallet state stay in its harness and reach these modules as arguments.
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).
@@ -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.2.0",
3
+ "version": "0.3.0",
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",
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ // The web-dapp layer of the web adapter: a strict EIP-1193 test wallet, the
4
+ // page script that wraps the app's provider and logs its wallet requests (or
5
+ // injects a provider the host answers), the host's side of that binding, and
6
+ // the request log that wallet assertions read. Product policy (typed data to
7
+ // refuse, the injected wallet's identity, forbidden log entries) is an input.
8
+
9
+ const {
10
+ DEFAULT_KNOWN_CHAINS,
11
+ READ_ONLY_RPC_METHODS,
12
+ createStrictWallet,
13
+ } = require('./strict-wallet.cjs');
14
+ const {
15
+ ERROR_CATEGORIES,
16
+ LOGGED_METHODS,
17
+ LOG_BINDING,
18
+ PAGE_MARKER,
19
+ REQUEST_BINDING,
20
+ RESOLVE_FN,
21
+ pageReadyExpression,
22
+ pageScriptSource,
23
+ } = require('./page-script.cjs');
24
+ const { PENDING_CALL_TTL_MS, createWalletRequestBinding } = require('./wallet-requests.cjs');
25
+ const {
26
+ awaitSignatureLog,
27
+ evaluateSignatureLog,
28
+ isTypedDataRequest,
29
+ parseLog,
30
+ readCursor,
31
+ readLog,
32
+ resetWindow,
33
+ shortPrimaryType,
34
+ summarize,
35
+ windowSinceCursor,
36
+ writeCursor,
37
+ writeLogArtifact,
38
+ } = require('./signature-log.cjs');
39
+
40
+ /**
41
+ * @typedef {import('./page-script.cjs').TypedDataRefusal} TypedDataRefusal
42
+ * @typedef {import('./page-script.cjs').InjectedWallet} InjectedWallet
43
+ * @typedef {import('./signature-log.cjs').SignatureLog} SignatureLog
44
+ * @typedef {import('./signature-log.cjs').SignaturePolicy} SignaturePolicy
45
+ * @typedef {import('./signature-log.cjs').SignatureSummary} SignatureSummary
46
+ * @typedef {import('./signature-log.cjs').LogEntry} LogEntry
47
+ */
48
+
49
+ module.exports = {
50
+ DEFAULT_KNOWN_CHAINS,
51
+ ERROR_CATEGORIES,
52
+ LOGGED_METHODS,
53
+ LOG_BINDING,
54
+ PAGE_MARKER,
55
+ PENDING_CALL_TTL_MS,
56
+ READ_ONLY_RPC_METHODS,
57
+ REQUEST_BINDING,
58
+ RESOLVE_FN,
59
+ awaitSignatureLog,
60
+ createStrictWallet,
61
+ createWalletRequestBinding,
62
+ evaluateSignatureLog,
63
+ isTypedDataRequest,
64
+ pageReadyExpression,
65
+ pageScriptSource,
66
+ parseLog,
67
+ readCursor,
68
+ readLog,
69
+ resetWindow,
70
+ shortPrimaryType,
71
+ summarize,
72
+ windowSinceCursor,
73
+ writeCursor,
74
+ writeLogArtifact,
75
+ };