@reclaimprotocol/agent 0.1.10 → 0.1.12
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/lib/authoring/tools.js +67 -0
- package/lib/mcp/tools/agent/find.js +41 -16
- package/lib/mcp/tools/agent/propose.js +4 -4
- package/lib/old/client.d.ts +31 -5
- package/lib/old/client.js +33 -10
- package/lib/old/to-register.d.ts +2 -0
- package/lib/old/to-register.js +9 -5
- package/lib/old/tools/build.js +12 -2
- package/lib/old/tools/check-app-status.d.ts +8 -0
- package/lib/old/tools/check-app-status.js +27 -0
- package/lib/old/tools/get-provider-info.d.ts +10 -7
- package/lib/old/tools/get-provider-info.js +21 -15
- package/lib/old/tools/issue-app-credentials.d.ts +11 -0
- package/lib/old/tools/issue-app-credentials.js +35 -0
- package/lib/old/tools/js-sdk-integration-guide.d.ts +2 -0
- package/lib/old/tools/js-sdk-integration-guide.js +141 -0
- package/lib/old/tools/link-app.d.ts +10 -0
- package/lib/old/tools/link-app.js +72 -0
- package/lib/old/tools/publish.js +89 -126
- package/lib/old/tools/scaffold-demo-server.d.ts +2 -0
- package/lib/old/tools/scaffold-demo-server.js +242 -0
- package/lib/old/tools/verify-sdk-setup.d.ts +9 -0
- package/lib/old/tools/verify-sdk-setup.js +128 -0
- package/lib/provider/dom-xpath.js +178 -25
- package/lib/provider/draft.d.ts +6 -0
- package/lib/provider/draft.js +54 -5
- package/lib/provider/matcher.d.ts +4 -0
- package/lib/provider/matcher.js +6 -0
- package/lib/provider/to-version.d.ts +13 -0
- package/package.json +2 -1
- package/src/authoring/tools.ts +76 -0
- package/src/mcp/tools/agent/find.ts +44 -16
- package/src/mcp/tools/agent/propose.ts +4 -4
- package/src/old/client.ts +60 -18
- package/src/old/to-register.ts +11 -5
- package/src/old/tools/build.ts +12 -2
- package/src/old/tools/check-app-status.ts +33 -0
- package/src/old/tools/get-provider-info.ts +21 -15
- package/src/old/tools/issue-app-credentials.ts +42 -0
- package/src/old/tools/js-sdk-integration-guide.ts +147 -0
- package/src/old/tools/link-app.ts +88 -0
- package/src/old/tools/publish.ts +98 -158
- package/src/old/tools/scaffold-demo-server.ts +265 -0
- package/src/old/tools/verify-sdk-setup.ts +158 -0
- package/src/provider/dom-xpath.ts +227 -27
- package/src/provider/draft.ts +59 -5
- package/src/provider/matcher.ts +7 -0
- package/src/provider/to-version.ts +13 -0
- package/src/old/tools/get-version-config.ts +0 -56
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verified against `@reclaimprotocol/js-sdk` v5.8.1 source
|
|
5
|
+
* (`reclaim-js-sdk/src/Reclaim.ts`) — NOT copied unchecked from the archived
|
|
6
|
+
* `reclaim-mcp-server`'s guide, which predates several of these signatures.
|
|
7
|
+
* Corrections that matter most, all called out inline below:
|
|
8
|
+
* - `getRequestUrl()` and `ReclaimProofRequest.fromJsonString()` are ASYNC.
|
|
9
|
+
* - `setContext` is current; `addContext` is deprecated (calls setContext).
|
|
10
|
+
* - `verifyProof`'s second arg (`{ providerId, providerVersion? }`) is
|
|
11
|
+
* REQUIRED, not optional.
|
|
12
|
+
* - The verified result's flag is `isVerified` (camelCase), not
|
|
13
|
+
* `is_verified`.
|
|
14
|
+
* - A raw webhook body is TEXT — it must be JSON-parsed (and URL-decoded if
|
|
15
|
+
* form-encoded) into a `Proof`/`Proof[]` before calling `verifyProof`.
|
|
16
|
+
*/
|
|
17
|
+
const GUIDE = `# @reclaimprotocol/js-sdk — backend integration guide
|
|
18
|
+
|
|
19
|
+
Keep \`appSecret\` on your BACKEND only. Everything below runs server-side;
|
|
20
|
+
the browser only ever sees the \`requestUrl\`.
|
|
21
|
+
|
|
22
|
+
## Testing locally, without deploying anything
|
|
23
|
+
|
|
24
|
+
- **providerId \`"example"\`**: a canonical built-in smoke-test provider —
|
|
25
|
+
no need to author/publish your own provider just to confirm the SDK
|
|
26
|
+
plumbing works end-to-end. Swap in your real providerId once this passes.
|
|
27
|
+
- **ngrok for the callback URL**: \`setAppCallbackUrl\` needs a URL Reclaim's
|
|
28
|
+
servers can reach over the public internet — \`localhost\` will never
|
|
29
|
+
receive the callback. Run \`ngrok http 3000\` (or your port) alongside your
|
|
30
|
+
local server and set \`PUBLIC_URL\` to the \`https://<id>.ngrok-free.app\`
|
|
31
|
+
URL it prints. Without this, only the frontend-side flow (opening
|
|
32
|
+
\`requestUrl\` and polling/reading the result client-side) will work — the
|
|
33
|
+
async webhook never lands.
|
|
34
|
+
- \`scaffold_reclaim_demo_server\` defaults \`providerId\` to \`"example"\` and
|
|
35
|
+
its README calls out the ngrok step — use it to get a working local loop
|
|
36
|
+
before wiring in a real provider.
|
|
37
|
+
|
|
38
|
+
## 1. Install
|
|
39
|
+
|
|
40
|
+
\`\`\`bash
|
|
41
|
+
npm install @reclaimprotocol/js-sdk
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
## 2. Create a proof request (backend)
|
|
45
|
+
|
|
46
|
+
\`\`\`js
|
|
47
|
+
import { ReclaimProofRequest } from "@reclaimprotocol/js-sdk";
|
|
48
|
+
|
|
49
|
+
const reclaimProofRequest = await ReclaimProofRequest.init(
|
|
50
|
+
process.env.RECLAIM_APP_ID,
|
|
51
|
+
process.env.RECLAIM_APP_SECRET,
|
|
52
|
+
providerId,
|
|
53
|
+
{
|
|
54
|
+
// acceptAiProviders: true, // allow AI-verified providers
|
|
55
|
+
// useAppClip: true, // mobile app-clip flow instead of a browser popup
|
|
56
|
+
// providerVersion: "1.0.0", // pin a specific provider version
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Deliver the verified proof back to your server asynchronously instead of
|
|
61
|
+
// (or in addition to) reading it from the frontend callback:
|
|
62
|
+
reclaimProofRequest.setAppCallbackUrl(
|
|
63
|
+
\`\${process.env.PUBLIC_URL}/webhooks/reclaim\`,
|
|
64
|
+
true, // jsonProofResponse: send the callback body as JSON, not form-encoded
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Optional: attach your own correlation id / metadata, retrievable later.
|
|
68
|
+
reclaimProofRequest.setContext(sessionId, JSON.stringify({ createdAt: Date.now() }));
|
|
69
|
+
|
|
70
|
+
// IMPORTANT: getRequestUrl() is ASYNC — await it.
|
|
71
|
+
const requestUrl = await reclaimProofRequest.getRequestUrl();
|
|
72
|
+
|
|
73
|
+
// Hand the requestUrl to the frontend to open in a new tab/window/webview.
|
|
74
|
+
// If you need to rehydrate this request object later (e.g. in the webhook
|
|
75
|
+
// handler, in a different process), persist reclaimProofRequest.toJsonString()
|
|
76
|
+
// (sync) and rehydrate with:
|
|
77
|
+
// const req = await ReclaimProofRequest.fromJsonString(json); // ASYNC
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
## 3. Frontend: open the request URL
|
|
81
|
+
|
|
82
|
+
Fetch \`requestUrl\` from your backend, then open it (popup/new tab/QR code) —
|
|
83
|
+
never construct it in the browser, since that would require the appSecret
|
|
84
|
+
client-side. Trigger the fetch+open from a real user click (not a page-load
|
|
85
|
+
effect) or some browsers block the popup.
|
|
86
|
+
|
|
87
|
+
## 4. Verify the callback (backend webhook)
|
|
88
|
+
|
|
89
|
+
\`\`\`js
|
|
90
|
+
import { verifyProof } from "@reclaimprotocol/js-sdk";
|
|
91
|
+
|
|
92
|
+
app.post("/webhooks/reclaim", express.json(), async (req, res) => {
|
|
93
|
+
// req.body is already a parsed object here because jsonProofResponse was
|
|
94
|
+
// true above AND express.json() parsed it. If you did NOT set
|
|
95
|
+
// jsonProofResponse (or aren't using express.json()), the body arrives as
|
|
96
|
+
// form-encoded/raw text and must be JSON.parse()'d (and decodeURIComponent'd
|
|
97
|
+
// if form-encoded) into a Proof / Proof[] before calling verifyProof.
|
|
98
|
+
const proof = req.body;
|
|
99
|
+
|
|
100
|
+
// The second argument is REQUIRED — at minimum providerId.
|
|
101
|
+
const result = await verifyProof(proof, {
|
|
102
|
+
providerId,
|
|
103
|
+
// providerVersion, // optional, pin if you pinned it in init()
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!result.isVerified) { // NOTE: isVerified, not is_verified
|
|
107
|
+
console.error("Proof verification failed:", result.error);
|
|
108
|
+
return res.status(400).json({ verified: false, error: result.error });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log("Verified data:", result.data, result.publicData);
|
|
112
|
+
res.status(200).json({ verified: true });
|
|
113
|
+
});
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
## Gotchas
|
|
117
|
+
|
|
118
|
+
- \`getRequestUrl()\` and \`ReclaimProofRequest.fromJsonString()\` are async —
|
|
119
|
+
a missing \`await\` silently hands you a Promise instead of a string/instance.
|
|
120
|
+
- \`verifyProof\`'s config arg is mandatory; omitting \`providerId\` throws.
|
|
121
|
+
- Prefer \`setContext\` over the deprecated \`addContext\` (same signature).
|
|
122
|
+
- \`ReclaimProofRequest.init\` needs network access to the Reclaim backend at
|
|
123
|
+
call time — don't call it at module load / cold-start if your platform pools
|
|
124
|
+
processes long-term; call it per-request instead.
|
|
125
|
+
- Use \`check_app_status\` (this MCP server) to confirm your app is linked
|
|
126
|
+
and not still sandbox-limited before debugging "verification never lands"
|
|
127
|
+
issues.
|
|
128
|
+
`
|
|
129
|
+
|
|
130
|
+
export function getJsSdkIntegrationGuideTool(): RegisteredTool {
|
|
131
|
+
return defineTool<Record<string, never>>(
|
|
132
|
+
{
|
|
133
|
+
name: 'get_js_sdk_integration_guide',
|
|
134
|
+
description:
|
|
135
|
+
'Return a verified, copy-pasteable guide for integrating ' +
|
|
136
|
+
'@reclaimprotocol/js-sdk (backend proof request + callback ' +
|
|
137
|
+
'verification), checked against the current SDK source rather than ' +
|
|
138
|
+
'assumed from older docs. Covers testing locally without deploying: ' +
|
|
139
|
+
'the built-in "example" providerId for a quick smoke test, and using ' +
|
|
140
|
+
'ngrok to get a callback URL Reclaim\'s servers can actually reach. ' +
|
|
141
|
+
'Use scaffold_reclaim_demo_server to generate a runnable local demo ' +
|
|
142
|
+
'instead of copy-pasting by hand.',
|
|
143
|
+
inputSchema: { type: 'object', properties: {} },
|
|
144
|
+
},
|
|
145
|
+
async() => ({ guide: GUIDE }),
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Wallet } from 'ethers'
|
|
2
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
3
|
+
import type { ReclaimOldClient } from '../client.ts'
|
|
4
|
+
|
|
5
|
+
/** Byte-for-byte the same format as the backend's `buildLinkMessage` in
|
|
6
|
+
* `application.controller.ts` — MUST stay in lockstep with it. */
|
|
7
|
+
function buildLinkMessage(appId: string, userId: string, nonce: string): string {
|
|
8
|
+
return `Link ${appId} to account ${userId} | nonce: ${nonce}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The uid the backend binds into the signed message — `req.user.uid` there.
|
|
12
|
+
* For a bearer identity this comes back from `POST /users/login`; for an eth
|
|
13
|
+
* identity, `OldIdentity.uid` (already `eth:<address>`) IS that uid. */
|
|
14
|
+
async function resolveUserId(client: ReclaimOldClient): Promise<string> {
|
|
15
|
+
const identity = client.getIdentity()
|
|
16
|
+
if(!identity) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'Not authenticated against old devtools. Call reclaim_authenticate first.',
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if(identity.kind === 'eth') {
|
|
23
|
+
return identity.uid
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const res = await client.loginWithToken() as { user?: { uid?: string } }
|
|
27
|
+
const uid = res?.user?.uid
|
|
28
|
+
if(!uid) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'Could not resolve your account uid from POST /users/login — ' +
|
|
31
|
+
'unexpected response shape.',
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return uid
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Link an unassociated app (minted via `issue_app_credentials`, or any
|
|
40
|
+
* eth-keypair app you already hold the private key for) to the currently
|
|
41
|
+
* authenticated dashboard account. Proves ownership by signing a one-time
|
|
42
|
+
* challenge with the app's private key — the private key itself is only
|
|
43
|
+
* ever used locally to sign, never sent to the server.
|
|
44
|
+
*/
|
|
45
|
+
export function linkAppTool(client: ReclaimOldClient): RegisteredTool {
|
|
46
|
+
return defineTool<{ appId: string, appSecret: string }>(
|
|
47
|
+
{
|
|
48
|
+
name: 'link_app_to_account',
|
|
49
|
+
description:
|
|
50
|
+
'Link an eth-keypair app to your authenticated dashboard account — ' +
|
|
51
|
+
'use this to keep using an appId/appSecret you already have ' +
|
|
52
|
+
'(instead of minting a fresh one via issue_app_credentials). ' +
|
|
53
|
+
'Requires reclaim_authenticate first. Fetches a one-time nonce, ' +
|
|
54
|
+
'signs `Link <appId> to account <userId> | nonce: <nonce>` locally ' +
|
|
55
|
+
'with `appSecret` (EIP-191, never transmitted), and submits the ' +
|
|
56
|
+
'signature. Fails with APP_ALREADY_LINKED if the app is already ' +
|
|
57
|
+
'linked to a (possibly different) account.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
appId: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'The eth-address appId to link.',
|
|
64
|
+
},
|
|
65
|
+
appSecret: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description:
|
|
68
|
+
'The app\'s raw eth private key — used only locally to sign ' +
|
|
69
|
+
'the link challenge, never sent to the server.',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['appId', 'appSecret'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
async({ appId, appSecret }) => {
|
|
76
|
+
const userId = await resolveUserId(client)
|
|
77
|
+
const { nonce } = await client.getLinkNonce()
|
|
78
|
+
const message = buildLinkMessage(appId, userId, nonce)
|
|
79
|
+
const signature = await new Wallet(appSecret).signMessage(message)
|
|
80
|
+
await client.linkAppToAccount(appId, signature, nonce)
|
|
81
|
+
return {
|
|
82
|
+
linked: true,
|
|
83
|
+
appId,
|
|
84
|
+
note: 'App linked to your account. Run check_app_status to confirm.',
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
}
|
package/src/old/tools/publish.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
bumpPatchString,
|
|
13
13
|
extractVersions,
|
|
14
14
|
injectedRequestDataFrom,
|
|
15
|
-
type OldInjectedRequestData,
|
|
16
15
|
type OldProviderOpts,
|
|
17
16
|
providerToConfigBody,
|
|
18
17
|
providerToRegisterBody,
|
|
@@ -20,66 +19,6 @@ import {
|
|
|
20
19
|
|
|
21
20
|
const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture'
|
|
22
21
|
|
|
23
|
-
/** Fields with no partial-patch equivalent on this backend: a version always
|
|
24
|
-
* replaces the WHOLE `providerConfig`, so omitting one of these on
|
|
25
|
-
* add-version must carry forward the current value rather than publish it
|
|
26
|
-
* blank. `allowedInjectedRequestData` is handled alongside these (further
|
|
27
|
-
* down, inline) since it needs translation when freshly authored, unlike
|
|
28
|
-
* these — which pass through unchanged either way. */
|
|
29
|
-
const PRESERVABLE_KEYS = [
|
|
30
|
-
'customInjection',
|
|
31
|
-
'userAgent',
|
|
32
|
-
'pageTitle',
|
|
33
|
-
'stepsToFollow',
|
|
34
|
-
'useIncognitoWebview',
|
|
35
|
-
'extensionConfig',
|
|
36
|
-
] as const
|
|
37
|
-
|
|
38
|
-
/** Pull `providerConfig` out of a `get_provider_version_config` /
|
|
39
|
-
* `getProviderConfig` response, tolerant of a missing/malformed shape. */
|
|
40
|
-
function extractProviderConfig(
|
|
41
|
-
resp: unknown,
|
|
42
|
-
): Record<string, unknown> | undefined {
|
|
43
|
-
if(resp && typeof resp === 'object') {
|
|
44
|
-
const cfg = (resp as { providerConfig?: unknown }).providerConfig
|
|
45
|
-
if(cfg && typeof cfg === 'object') {
|
|
46
|
-
return cfg as Record<string, unknown>
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return undefined
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Prefer the caller's explicit value; otherwise fall back to `current`'s
|
|
54
|
-
* value (if any) and record `fieldName` as carried forward.
|
|
55
|
-
*
|
|
56
|
-
* `current === null` is treated the SAME as `undefined` (nothing to carry),
|
|
57
|
-
* not as a real value to preserve. These are all optional fields with no
|
|
58
|
-
* Prisma default — a version where one was never set reads back as
|
|
59
|
-
* `null`, not a missing key, because Prisma normalizes an absent optional
|
|
60
|
-
* field to `null` on read. Carrying that `null` "forward" would falsely
|
|
61
|
-
* claim the field was preserved when really nothing was ever there — it
|
|
62
|
-
* most likely means an EARLIER version already lost it (e.g. published
|
|
63
|
-
* before this field had carry-forward support at all), not that this call
|
|
64
|
-
* is doing anything wrong. */
|
|
65
|
-
function resolveField<T>(
|
|
66
|
-
explicit: T | undefined,
|
|
67
|
-
current: unknown,
|
|
68
|
-
fieldName: string,
|
|
69
|
-
carried: string[],
|
|
70
|
-
): T | undefined {
|
|
71
|
-
if(explicit !== undefined) {
|
|
72
|
-
return explicit
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if(current !== undefined && current !== null) {
|
|
76
|
-
carried.push(fieldName)
|
|
77
|
-
return current as T
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return undefined
|
|
81
|
-
}
|
|
82
|
-
|
|
83
22
|
/**
|
|
84
23
|
* The old backend lets you add a version only to a provider YOU own. A
|
|
85
24
|
* `providerId` for a public provider (or anyone else's) comes back as
|
|
@@ -166,23 +105,19 @@ export function createVersionFromCaptureTool(
|
|
|
166
105
|
'For data gated behind login/navigation, pass `customInjection` — a ' +
|
|
167
106
|
'page-injection script (old-devtools\' name for the builder\'s ' +
|
|
168
107
|
'jsUserScripts) that drives the page to where the request fires. \n\n' +
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'explicit value (`""`, `[]`, `false`, ...) to intentionally clear ' +
|
|
183
|
-
'one of them instead, or call get_provider_version_config FIRST ' +
|
|
184
|
-
'to see exactly what is live today, rather than assuming this ' +
|
|
185
|
-
'call recovered something that may already be gone. ' +
|
|
108
|
+
'BY DESIGN, this backend only ever CREATES A NEW VERSION (POST) — ' +
|
|
109
|
+
'there is no partial-patch/edit endpoint, and this tool does NOT ' +
|
|
110
|
+
'read or carry anything forward from the previous version. Every ' +
|
|
111
|
+
'new version replaces the WHOLE providerConfig with exactly what ' +
|
|
112
|
+
'you pass in THIS call. Therefore any field you want present in the ' +
|
|
113
|
+
'new version MUST be supplied here — `customInjection`, `userAgent`, ' +
|
|
114
|
+
'`pageTitle`, `allowedInjectedRequestData`, `stepsToFollow`, ' +
|
|
115
|
+
'`useIncognitoWebview`, `extensionConfig`, and `requestData` (via ' +
|
|
116
|
+
'`provider`/`providers`). Anything you OMIT is published blank/' +
|
|
117
|
+
'default in the new version, NOT preserved. To change one field ' +
|
|
118
|
+
'while keeping the rest, first read the current version with ' +
|
|
119
|
+
'get_provider_info, then pass the full desired config (the existing ' +
|
|
120
|
+
'values plus your change) back into this call. ' +
|
|
186
121
|
'`allowedInjectedRequestData`/`useIncognitoWebview`/' +
|
|
187
122
|
'`extensionConfig` only take effect on add-version — the register ' +
|
|
188
123
|
'endpoint (no `providerId`) ignores them. \n\n' +
|
|
@@ -213,8 +148,15 @@ export function createVersionFromCaptureTool(
|
|
|
213
148
|
'Multiple proven drafts → one multi-request version (each ' +
|
|
214
149
|
'becomes a parallel `requestData` entry, all verified ' +
|
|
215
150
|
'together). Each item has the same shape as `provider`. Pass ' +
|
|
216
|
-
'this OR `provider`, not both.'
|
|
217
|
-
|
|
151
|
+
'this OR `provider`, not both. May be omitted (or empty) ' +
|
|
152
|
+
'together with `provider` ONLY when adding a version to an ' +
|
|
153
|
+
'EXISTING provider (`providerId` set) — this publishes with ' +
|
|
154
|
+
'zero `requestData` entries, for a pure customInjection/' +
|
|
155
|
+
'HAWKEYE provider that relies entirely on ' +
|
|
156
|
+
'`allowedInjectedRequestData` for validation instead of a ' +
|
|
157
|
+
'static requestData stub. Registering a brand-new provider ' +
|
|
158
|
+
'always needs at least one draft (its name comes from the ' +
|
|
159
|
+
'first one).',
|
|
218
160
|
items: {
|
|
219
161
|
type: 'object',
|
|
220
162
|
properties: {
|
|
@@ -316,6 +258,28 @@ export function createVersionFromCaptureTool(
|
|
|
316
258
|
description:
|
|
317
259
|
'Fail validation if none match. Default true.',
|
|
318
260
|
},
|
|
261
|
+
templateParams: {
|
|
262
|
+
type: 'array',
|
|
263
|
+
items: { type: 'string' },
|
|
264
|
+
description:
|
|
265
|
+
'Names of `${var}` placeholders used in this ' +
|
|
266
|
+
'template\'s `responseMatches`/`responseRedactions` ' +
|
|
267
|
+
'that should be substituted at verify time from the ' +
|
|
268
|
+
'submitted proof\'s own witness params. Omit (or ' +
|
|
269
|
+
'leave empty) if the template has no such ' +
|
|
270
|
+
'placeholders.',
|
|
271
|
+
},
|
|
272
|
+
templateParamsMode: {
|
|
273
|
+
type: 'string',
|
|
274
|
+
enum: ['separate', 'merge'],
|
|
275
|
+
description:
|
|
276
|
+
'How multiple values for `templateParams` expand ' +
|
|
277
|
+
'into request specs. `separate` (default): one ' +
|
|
278
|
+
'independent spec per value, each matching its own ' +
|
|
279
|
+
'proof. `merge`: fold all values into ONE spec with ' +
|
|
280
|
+
'one match/redaction entry per value, matching a ' +
|
|
281
|
+
'single proof that bundles them all into one claim.',
|
|
282
|
+
},
|
|
319
283
|
},
|
|
320
284
|
required: ['url', 'method'],
|
|
321
285
|
},
|
|
@@ -344,37 +308,52 @@ export function createVersionFromCaptureTool(
|
|
|
344
308
|
async(args) => {
|
|
345
309
|
// Exactly one of `provider` / `providers` — normalize to an array so a
|
|
346
310
|
// multi-request capture publishes every request, not just the first.
|
|
311
|
+
// Omitting both is allowed too, but ONLY when adding a version to an
|
|
312
|
+
// EXISTING provider (`providerId` set): that publishes with zero
|
|
313
|
+
// `requestData` entries, which only makes sense for a pure
|
|
314
|
+
// customInjection/HAWKEYE provider whose claims are validated entirely
|
|
315
|
+
// via `allowedInjectedRequestData` instead of a static requestData stub.
|
|
316
|
+
// Registering a brand-new provider (no `providerId`) still needs at
|
|
317
|
+
// least one draft — its name comes from the first one.
|
|
347
318
|
const hasMany = Array.isArray(args.providers) && args.providers.length > 0
|
|
348
319
|
const hasOne = !!args.provider
|
|
349
|
-
if(hasMany
|
|
320
|
+
if(hasMany && hasOne) {
|
|
350
321
|
throw new Boom(
|
|
351
|
-
'Pass
|
|
352
|
-
'(an array of 1+ drafts).',
|
|
322
|
+
'Pass at most one of `provider` (a single draft) or `providers` ' +
|
|
323
|
+
'(an array of 1+ drafts) — not both.',
|
|
353
324
|
{ statusCode: 400 },
|
|
354
325
|
)
|
|
355
326
|
}
|
|
356
327
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
opts.geoLocation = args.geoLocation
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if(args.useProxy !== undefined) {
|
|
369
|
-
opts.useProxy = args.useProxy
|
|
328
|
+
if(!hasMany && !hasOne && !args.providerId) {
|
|
329
|
+
throw new Boom(
|
|
330
|
+
'Pass at least one of `provider`/`providers` when registering a ' +
|
|
331
|
+
'new provider (its name comes from the first draft). Omitting ' +
|
|
332
|
+
'both is only allowed when adding a version to an EXISTING ' +
|
|
333
|
+
'provider via `providerId`.',
|
|
334
|
+
{ statusCode: 400 },
|
|
335
|
+
)
|
|
370
336
|
}
|
|
371
337
|
|
|
372
|
-
|
|
373
|
-
opts.providerType = args.providerType
|
|
374
|
-
}
|
|
338
|
+
const providers = hasMany ? args.providers! : (hasOne ? [args.provider!] : [])
|
|
375
339
|
|
|
376
|
-
|
|
377
|
-
|
|
340
|
+
const opts: OldProviderOpts = {
|
|
341
|
+
initialUrl: args.initialUrl,
|
|
342
|
+
description: args.description,
|
|
343
|
+
geoLocation: args.geoLocation,
|
|
344
|
+
useProxy: args.useProxy,
|
|
345
|
+
providerType: args.providerType,
|
|
346
|
+
customInjection: args.customInjection,
|
|
347
|
+
userAgent: args.userAgent,
|
|
348
|
+
pageTitle: args.pageTitle,
|
|
349
|
+
stepsToFollow: args.stepsToFollow,
|
|
350
|
+
useIncognitoWebview: args.useIncognitoWebview,
|
|
351
|
+
extensionConfig: args.extensionConfig,
|
|
352
|
+
allowedInjectedRequestData: args.allowedInjectedRequestData
|
|
353
|
+
? injectedRequestDataFrom(
|
|
354
|
+
args.allowedInjectedRequestData,
|
|
355
|
+
)
|
|
356
|
+
: undefined
|
|
378
357
|
}
|
|
379
358
|
|
|
380
359
|
// Non-blocking advisory per request — never alters or blocks the
|
|
@@ -414,27 +393,15 @@ export function createVersionFromCaptureTool(
|
|
|
414
393
|
.filter((k) => args[k] !== undefined)
|
|
415
394
|
if(ignoredOnRegister.length) {
|
|
416
395
|
notes.push(
|
|
417
|
-
`${ignoredOnRegister.join(', ')}
|
|
418
|
-
|
|
419
|
-
|
|
396
|
+
`${ignoredOnRegister.join(', ')} ` +
|
|
397
|
+
`${ignoredOnRegister.length > 1 ? 'are' : 'is'} ` +
|
|
398
|
+
'ignored on register (no providerId) — the old backend only ' +
|
|
420
399
|
'reads them when adding a version. Publish once to create ' +
|
|
421
400
|
'the provider, then call this again WITH the returned ' +
|
|
422
401
|
'providerId to set them.',
|
|
423
402
|
)
|
|
424
403
|
}
|
|
425
404
|
|
|
426
|
-
if(args.userAgent !== undefined) {
|
|
427
|
-
opts.userAgent = args.userAgent
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if(args.pageTitle !== undefined) {
|
|
431
|
-
opts.pageTitle = args.pageTitle
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if(args.stepsToFollow !== undefined) {
|
|
435
|
-
opts.stepsToFollow = args.stepsToFollow
|
|
436
|
-
}
|
|
437
|
-
|
|
438
405
|
const body = providerToRegisterBody(providers, opts)
|
|
439
406
|
return withNotes(await client.registerProvider(body))
|
|
440
407
|
}
|
|
@@ -445,48 +412,12 @@ export function createVersionFromCaptureTool(
|
|
|
445
412
|
// workaround instead of a bare permission error.
|
|
446
413
|
const providerId = args.providerId
|
|
447
414
|
try {
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|| args.allowedInjectedRequestData === undefined
|
|
455
|
-
const current = needsCurrent
|
|
456
|
-
? extractProviderConfig(await client.getProviderConfig(providerId))
|
|
457
|
-
: undefined
|
|
458
|
-
|
|
459
|
-
const carried: string[] = []
|
|
460
|
-
for(const key of PRESERVABLE_KEYS) {
|
|
461
|
-
const resolved = resolveField(args[key], current?.[key], key, carried)
|
|
462
|
-
if(resolved !== undefined) {
|
|
463
|
-
(opts as unknown as Record<string, unknown>)[key] = resolved
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const freshInjected = args.allowedInjectedRequestData !== undefined
|
|
468
|
-
? injectedRequestDataFrom(args.allowedInjectedRequestData)
|
|
469
|
-
: undefined
|
|
470
|
-
const resolvedInjected = resolveField<OldInjectedRequestData[]>(
|
|
471
|
-
freshInjected,
|
|
472
|
-
current?.allowedInjectedRequestData,
|
|
473
|
-
'allowedInjectedRequestData',
|
|
474
|
-
carried,
|
|
475
|
-
)
|
|
476
|
-
if(resolvedInjected !== undefined) {
|
|
477
|
-
opts.allowedInjectedRequestData = resolvedInjected
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if(carried.length) {
|
|
481
|
-
notes.push(
|
|
482
|
-
'Not passed, carried forward unchanged from the current ' +
|
|
483
|
-
`published version: ${carried.join(', ')}. Pass an explicit ` +
|
|
484
|
-
'value ("", [], false, ...) to override any of them, or ' +
|
|
485
|
-
'call get_provider_version_config first to see what is ' +
|
|
486
|
-
'live today.',
|
|
487
|
-
)
|
|
488
|
-
}
|
|
489
|
-
|
|
415
|
+
// By design this always POSTs a brand-new version and publishes
|
|
416
|
+
// EXACTLY the config assembled above — nothing is read back or
|
|
417
|
+
// carried forward. Any field the caller omitted is already absent
|
|
418
|
+
// from `opts` and will be blank/default in the new version. To
|
|
419
|
+
// preserve existing fields, the caller must have read them via
|
|
420
|
+
// get_provider_info and passed them back in.
|
|
490
421
|
if(args.customInjection !== undefined && opts.customInjection?.trim()) {
|
|
491
422
|
notes.push(
|
|
492
423
|
'customInjection will run in the verification client before ' +
|
|
@@ -495,6 +426,15 @@ export function createVersionFromCaptureTool(
|
|
|
495
426
|
)
|
|
496
427
|
}
|
|
497
428
|
|
|
429
|
+
notes.push(
|
|
430
|
+
'A NEW version was created with only the fields passed in this ' +
|
|
431
|
+
'call — nothing was carried forward from the previous version. ' +
|
|
432
|
+
'If you intended to keep other fields (customInjection, ' +
|
|
433
|
+
'userAgent, allowedInjectedRequestData, ...), read the prior ' +
|
|
434
|
+
'version with get_provider_info and re-publish with them ' +
|
|
435
|
+
'included.',
|
|
436
|
+
)
|
|
437
|
+
|
|
498
438
|
const versionsResp = await client.listVersions(providerId)
|
|
499
439
|
const version = bumpPatchString(extractVersions(versionsResp))
|
|
500
440
|
const versionInfo =
|