iframer-toolkit 0.11.0 → 0.11.2
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/README.md +5 -1
- package/dist/cli.js +682 -37
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/iframer.md +4 -1
package/README.md
CHANGED
|
@@ -227,6 +227,7 @@ Capture in a browser iframer doesn't control (T3 Code, Playwright MCP, ...):
|
|
|
227
227
|
--out <dir> Output directory (default: ./<host>-api/)
|
|
228
228
|
--keep Keep the capture file (default: deleted)
|
|
229
229
|
--no-knowledge Don't update ~/.iframer/knowledge
|
|
230
|
+
--strict Refuse on any secret-looking value instead of replacing it
|
|
230
231
|
--json One-line JSON summary
|
|
231
232
|
|
|
232
233
|
Browser windows:
|
|
@@ -267,6 +268,7 @@ Setup:
|
|
|
267
268
|
extension path Print the extension folder to load in chrome://extensions
|
|
268
269
|
extension token Print the pairing token (only needed to pair by hand)
|
|
269
270
|
update [--check] Update iframer via npm (--check: report only)
|
|
271
|
+
refresh Rewrite the in-page kit and the /iframer skill (update runs it)
|
|
270
272
|
remove [chromium|mcp|extension] Remove everything, or one piece
|
|
271
273
|
|
|
272
274
|
Browser:
|
|
@@ -339,9 +341,11 @@ iframer ingest --latest --typed # finds the capture in T3's downloads -> ./<host
|
|
|
339
341
|
```
|
|
340
342
|
|
|
341
343
|
- **Small for the agent:** the kit and the capture move as files, so neither lands in the agent's context. On the 40-endpoint test site, the whole install-capture-export round was about 550 tokens of tool calls.
|
|
342
|
-
- **Shapes, not values:** paths are templated (`/users/{id}`), bodies and responses become type shapes, headers become names. `ingest`
|
|
344
|
+
- **Shapes, not values:** paths are templated (`/users/{id}`), bodies and responses become type shapes, headers become names. `ingest` then swaps anything left that looks like a token, email or phone number for a placeholder, keeps the endpoint, and marks it with `privateValues`. `--strict` refuses the capture instead.
|
|
345
|
+
- **Identity, without the values:** the kit learns which auth headers the page sends and where it keeps each value (a localStorage key, a field inside it, a cookie, or page memory). api.json records that under `identity`, and `__ifk.call` rebuilds the headers from there for every replay, so the generated client acts as the logged-in user.
|
|
343
346
|
- **Output:** `api.json` (endpoint map with coverage), `client.js` (in-page client over `window.__ifk.call`), `client.d.ts` with `--typed`, and the domain's knowledge file.
|
|
344
347
|
- **Replay stays in the page:** HttpOnly cookies are invisible to page JS, so generated calls run through the tab. Same-origin only, GET unless approved, 50 calls per session, and a hard stop on 403, 429 or a captcha page.
|
|
348
|
+
- **Next.js:** dynamic route segments are named from the app's own route data, so `/wichitalife/dashboard` is saved as `/{communitySlug}/dashboard` and the client takes `communitySlug` as an argument. RSC responses (`text/x-component`) become data: the props the server passed to each client component, or a Server Action's return value. Server Actions are saved by export name and called with `window.__ifk.action(name, [args], {approved:true})`. The id is looked up again in the page's JS on every call, so it keeps working after a deploy.
|
|
345
349
|
- **Not complete:** worker requests, redirect hops, cross-origin iframes and Set-Cookie are invisible to the kit, and every report says so.
|
|
346
350
|
- **Survives the page:** after a full page load the agent re-runs one short `eval` and the capture continues. Works on sites that delete `window.sessionStorage` (Discord).
|
|
347
351
|
- **Other tools:** `--for playwright-mcp` or `--for any`. Tools without file upload can evaluate `iframer kit --print` instead, which costs about 17 KB per page load.
|
package/dist/cli.js
CHANGED
|
@@ -11621,10 +11621,12 @@ function pageKit(opts) {
|
|
|
11621
11621
|
var w = window;
|
|
11622
11622
|
var VERSION = 1;
|
|
11623
11623
|
var KEY = "__ifk_state";
|
|
11624
|
-
|
|
11624
|
+
var BUILD = opts.build || "dev";
|
|
11625
|
+
if (w.__ifk && w.__ifk.version === VERSION && w.__ifk.build === BUILD) {
|
|
11625
11626
|
var s0 = w.__ifk.summary();
|
|
11626
11627
|
return { installed: true, again: true, version: VERSION, seen: s0.seen };
|
|
11627
11628
|
}
|
|
11629
|
+
var replaced = null;
|
|
11628
11630
|
var mode = opts.mode === "passive" ? "passive" : "hook";
|
|
11629
11631
|
var hostRe = null;
|
|
11630
11632
|
try {
|
|
@@ -11652,9 +11654,39 @@ function pageKit(opts) {
|
|
|
11652
11654
|
}
|
|
11653
11655
|
}
|
|
11654
11656
|
var ss = store();
|
|
11657
|
+
if (w.__ifk && w.__ifk.stop) {
|
|
11658
|
+
replaced = w.__ifk.build || "old";
|
|
11659
|
+
try {
|
|
11660
|
+
var keep = {};
|
|
11661
|
+
[KEY, "__ifk", "__ifkApi"].forEach(function(k) {
|
|
11662
|
+
keep[k] = ss && ss.getItem(k);
|
|
11663
|
+
});
|
|
11664
|
+
w.__ifk.stop();
|
|
11665
|
+
Object.keys(keep).forEach(function(k) {
|
|
11666
|
+
if (keep[k] != null) ss.setItem(k, keep[k]);
|
|
11667
|
+
});
|
|
11668
|
+
} catch (e) {
|
|
11669
|
+
}
|
|
11670
|
+
}
|
|
11671
|
+
function lstore() {
|
|
11672
|
+
try {
|
|
11673
|
+
if (w.localStorage) return w.localStorage;
|
|
11674
|
+
} catch (e) {
|
|
11675
|
+
}
|
|
11676
|
+
try {
|
|
11677
|
+
store();
|
|
11678
|
+
return document.getElementById("__ifk_ss").contentWindow.localStorage;
|
|
11679
|
+
} catch (e) {
|
|
11680
|
+
return null;
|
|
11681
|
+
}
|
|
11682
|
+
}
|
|
11655
11683
|
var m = {};
|
|
11656
11684
|
var po = {};
|
|
11657
11685
|
var budget = { calls: 0, stopped: null };
|
|
11686
|
+
var authSrc = {};
|
|
11687
|
+
var authMem = {};
|
|
11688
|
+
var rp = {};
|
|
11689
|
+
var acts = {};
|
|
11658
11690
|
var restored = 0;
|
|
11659
11691
|
try {
|
|
11660
11692
|
var saved = JSON.parse(ss.getItem(KEY) || "null");
|
|
@@ -11662,6 +11694,8 @@ function pageKit(opts) {
|
|
|
11662
11694
|
m = saved.m || {};
|
|
11663
11695
|
po = saved.po || {};
|
|
11664
11696
|
if (saved.budget) budget = saved.budget;
|
|
11697
|
+
if (saved.authSrc) authSrc = saved.authSrc;
|
|
11698
|
+
if (saved.acts) acts = saved.acts;
|
|
11665
11699
|
restored = Object.keys(m).length;
|
|
11666
11700
|
}
|
|
11667
11701
|
} catch (e) {
|
|
@@ -11687,9 +11721,222 @@ function pageKit(opts) {
|
|
|
11687
11721
|
}
|
|
11688
11722
|
function tpl(p) {
|
|
11689
11723
|
return p.split("/").map(function(s) {
|
|
11724
|
+
var d = s;
|
|
11725
|
+
try {
|
|
11726
|
+
d = decodeURIComponent(s);
|
|
11727
|
+
} catch (e) {
|
|
11728
|
+
}
|
|
11729
|
+
if (s && rp.hasOwnProperty(d)) return "{" + rp[d] + "}";
|
|
11690
11730
|
return isValueLike(s) ? "{id}" : s;
|
|
11691
11731
|
}).join("/");
|
|
11692
11732
|
}
|
|
11733
|
+
var PARAM_RE = /\["([A-Za-z_][\w-]{0,40})","([^"\\\/]{1,80})","(?:d|c|oc|ci|dc)"/g;
|
|
11734
|
+
function learnParams(text) {
|
|
11735
|
+
if (!text || text.indexOf('","d"') < 0 && text.indexOf('","c"') < 0 && text.indexOf('","oc"') < 0) return;
|
|
11736
|
+
PARAM_RE.lastIndex = 0;
|
|
11737
|
+
var x;
|
|
11738
|
+
while (x = PARAM_RE.exec(text)) if (!rp.hasOwnProperty(x[2])) rp[x[2]] = x[1];
|
|
11739
|
+
}
|
|
11740
|
+
var nextIdx = 0;
|
|
11741
|
+
function scanNext() {
|
|
11742
|
+
try {
|
|
11743
|
+
var f = w.__next_f;
|
|
11744
|
+
if (f && f.length > nextIdx) {
|
|
11745
|
+
for (var i = nextIdx; i < f.length; i++) if (f[i] && typeof f[i][1] === "string") learnParams(f[i][1]);
|
|
11746
|
+
nextIdx = f.length;
|
|
11747
|
+
}
|
|
11748
|
+
} catch (e) {
|
|
11749
|
+
}
|
|
11750
|
+
try {
|
|
11751
|
+
var q = w.__NEXT_DATA__ && w.__NEXT_DATA__.query;
|
|
11752
|
+
if (q) Object.keys(q).forEach(function(k) {
|
|
11753
|
+
if (typeof q[k] === "string" && !rp.hasOwnProperty(q[k])) rp[q[k]] = k;
|
|
11754
|
+
});
|
|
11755
|
+
} catch (e) {
|
|
11756
|
+
}
|
|
11757
|
+
}
|
|
11758
|
+
function learnFromBody(b, u) {
|
|
11759
|
+
if (typeof b !== "string" || b.length > 2e4) return;
|
|
11760
|
+
var j;
|
|
11761
|
+
try {
|
|
11762
|
+
j = JSON.parse(b);
|
|
11763
|
+
} catch (e) {
|
|
11764
|
+
return;
|
|
11765
|
+
}
|
|
11766
|
+
var segs = {};
|
|
11767
|
+
(u.pathname + "/" + location.pathname).split("/").forEach(function(s) {
|
|
11768
|
+
if (s) {
|
|
11769
|
+
try {
|
|
11770
|
+
segs[decodeURIComponent(s)] = 1;
|
|
11771
|
+
} catch (e) {
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
});
|
|
11775
|
+
(function walk(v, d) {
|
|
11776
|
+
if (!v || typeof v !== "object" || d > 3) return;
|
|
11777
|
+
Object.keys(v).forEach(function(k) {
|
|
11778
|
+
var x = v[k];
|
|
11779
|
+
if (typeof x === "string" && x.length > 1 && segs[x] && /(id|slug|key|handle|code|name)$/i.test(k) && !rp.hasOwnProperty(x)) rp[x] = k;
|
|
11780
|
+
else walk(x, d + 1);
|
|
11781
|
+
});
|
|
11782
|
+
})(j, 0);
|
|
11783
|
+
}
|
|
11784
|
+
function parseFlight(t) {
|
|
11785
|
+
var rows = {};
|
|
11786
|
+
try {
|
|
11787
|
+
var b = new TextEncoder().encode(t), dec = new TextDecoder(), i = 0, n = b.length;
|
|
11788
|
+
while (i < n) {
|
|
11789
|
+
var c = i;
|
|
11790
|
+
while (c < n && b[c] !== 58 && b[c] !== 10) c++;
|
|
11791
|
+
if (c >= n) break;
|
|
11792
|
+
if (b[c] === 10) {
|
|
11793
|
+
i = c + 1;
|
|
11794
|
+
continue;
|
|
11795
|
+
}
|
|
11796
|
+
var id = dec.decode(b.subarray(i, c));
|
|
11797
|
+
var j = c + 1;
|
|
11798
|
+
if (b[j] === 84) {
|
|
11799
|
+
var cm = j + 1;
|
|
11800
|
+
while (cm < n && b[cm] !== 44) cm++;
|
|
11801
|
+
var len = parseInt(dec.decode(b.subarray(j + 1, cm)), 16) || 0;
|
|
11802
|
+
rows[id] = { text: dec.decode(b.subarray(cm + 1, cm + 1 + len)) };
|
|
11803
|
+
i = cm + 1 + len;
|
|
11804
|
+
if (b[i] === 10) i++;
|
|
11805
|
+
continue;
|
|
11806
|
+
}
|
|
11807
|
+
var nl = j;
|
|
11808
|
+
while (nl < n && b[nl] !== 10) nl++;
|
|
11809
|
+
var p = dec.decode(b.subarray(j, nl));
|
|
11810
|
+
i = nl + 1;
|
|
11811
|
+
if (!/^[0-9a-f]+$/i.test(id)) continue;
|
|
11812
|
+
var tag = (/^[A-Z]+/.exec(p) || [""])[0];
|
|
11813
|
+
try {
|
|
11814
|
+
rows[id] = { tag, v: JSON.parse(p.slice(tag.length)) };
|
|
11815
|
+
} catch (e) {
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
} catch (e) {
|
|
11819
|
+
}
|
|
11820
|
+
return rows;
|
|
11821
|
+
}
|
|
11822
|
+
function flightData(rows) {
|
|
11823
|
+
function resolve(v, d) {
|
|
11824
|
+
if (d > 30) return null;
|
|
11825
|
+
if (typeof v === "string") {
|
|
11826
|
+
if (v === "$undefined") return void 0;
|
|
11827
|
+
if (/^\$D/.test(v)) return v.slice(2);
|
|
11828
|
+
var r = /^\$@?([0-9a-f]+)$/.exec(v);
|
|
11829
|
+
if (r && rows[r[1]] && rows[r[1]].v !== void 0) return resolve(rows[r[1]].v, d + 1);
|
|
11830
|
+
if (r && rows[r[1]] && rows[r[1]].text !== void 0) return rows[r[1]].text;
|
|
11831
|
+
return v.charAt(0) === "$" && v.charAt(1) === "$" ? v.slice(1) : v;
|
|
11832
|
+
}
|
|
11833
|
+
if (Array.isArray(v)) return v.map(function(x) {
|
|
11834
|
+
return resolve(x, d + 1);
|
|
11835
|
+
});
|
|
11836
|
+
if (v && typeof v === "object") {
|
|
11837
|
+
var o = {};
|
|
11838
|
+
Object.keys(v).forEach(function(k) {
|
|
11839
|
+
o[k] = resolve(v[k], d + 1);
|
|
11840
|
+
});
|
|
11841
|
+
return o;
|
|
11842
|
+
}
|
|
11843
|
+
return v;
|
|
11844
|
+
}
|
|
11845
|
+
var r0 = rows["0"] && rows["0"].v;
|
|
11846
|
+
if (r0 && typeof r0 === "object" && !Array.isArray(r0) && typeof r0.a === "string" && /^\$@/.test(r0.a)) {
|
|
11847
|
+
return { action: resolve(r0.a, 0) };
|
|
11848
|
+
}
|
|
11849
|
+
var names = {};
|
|
11850
|
+
Object.keys(rows).forEach(function(id) {
|
|
11851
|
+
var r = rows[id];
|
|
11852
|
+
if (r.tag !== "I" || !r.v) return;
|
|
11853
|
+
var nm = Array.isArray(r.v) ? r.v[2] : r.v.name;
|
|
11854
|
+
names["$L" + id] = nm && nm !== "default" && nm !== "*" ? nm : "Component" + id;
|
|
11855
|
+
});
|
|
11856
|
+
var comps = {}, seen = 0;
|
|
11857
|
+
function walk(v, d) {
|
|
11858
|
+
if (d > 60 || seen > 400 || !v || typeof v !== "object") return;
|
|
11859
|
+
if (Array.isArray(v)) {
|
|
11860
|
+
if (v[0] === "$" && typeof v[1] === "string" && v.length === 4) {
|
|
11861
|
+
var props = v[3];
|
|
11862
|
+
if (props && typeof props === "object" && names[v[1]]) {
|
|
11863
|
+
var data = {}, has = false;
|
|
11864
|
+
Object.keys(props).forEach(function(k) {
|
|
11865
|
+
if (k !== "children") {
|
|
11866
|
+
data[k] = resolve(props[k], 0);
|
|
11867
|
+
has = true;
|
|
11868
|
+
}
|
|
11869
|
+
});
|
|
11870
|
+
if (has) {
|
|
11871
|
+
seen++;
|
|
11872
|
+
(comps[names[v[1]]] = comps[names[v[1]]] || []).push(data);
|
|
11873
|
+
}
|
|
11874
|
+
}
|
|
11875
|
+
if (props && typeof props === "object") walk(props.children, d + 1);
|
|
11876
|
+
return;
|
|
11877
|
+
}
|
|
11878
|
+
v.forEach(function(x) {
|
|
11879
|
+
walk(x, d + 1);
|
|
11880
|
+
});
|
|
11881
|
+
return;
|
|
11882
|
+
}
|
|
11883
|
+
Object.keys(v).forEach(function(k) {
|
|
11884
|
+
walk(v[k], d + 1);
|
|
11885
|
+
});
|
|
11886
|
+
}
|
|
11887
|
+
Object.keys(rows).forEach(function(id) {
|
|
11888
|
+
if (!rows[id].tag && rows[id].v !== void 0) walk(rows[id].v, 0);
|
|
11889
|
+
});
|
|
11890
|
+
Object.keys(comps).forEach(function(k) {
|
|
11891
|
+
if (comps[k].length === 1) comps[k] = comps[k][0];
|
|
11892
|
+
});
|
|
11893
|
+
return { components: comps };
|
|
11894
|
+
}
|
|
11895
|
+
function isFlight(ct) {
|
|
11896
|
+
return /text\/x-component/i.test(ct || "");
|
|
11897
|
+
}
|
|
11898
|
+
var ACT_RE = /\(\s*"([0-9a-f]{40,44})"\s*,[^"()]{0,200}?,\s*"([A-Za-z_$][\w$]*)"\s*\)/g;
|
|
11899
|
+
var actScan = null;
|
|
11900
|
+
function scanActions(fresh) {
|
|
11901
|
+
if (actScan && !fresh) return actScan;
|
|
11902
|
+
var srcs = {};
|
|
11903
|
+
try {
|
|
11904
|
+
[].slice.call(document.scripts).forEach(function(sc) {
|
|
11905
|
+
if (sc.src) srcs[sc.src] = 1;
|
|
11906
|
+
});
|
|
11907
|
+
} catch (e) {
|
|
11908
|
+
}
|
|
11909
|
+
try {
|
|
11910
|
+
performance.getEntriesByType("resource").forEach(function(r) {
|
|
11911
|
+
if (r.initiatorType === "script" || /\.js(\?|$)/.test(r.name)) srcs[r.name] = 1;
|
|
11912
|
+
});
|
|
11913
|
+
} catch (e) {
|
|
11914
|
+
}
|
|
11915
|
+
var found = {};
|
|
11916
|
+
actScan = Promise.all(Object.keys(srcs).filter(function(s) {
|
|
11917
|
+
var u = abs(s);
|
|
11918
|
+
return u && u.origin === location.origin && /\/_next\//.test(u.pathname);
|
|
11919
|
+
}).map(function(s) {
|
|
11920
|
+
return origFetch.call(w, s, { cache: fresh ? "no-cache" : "default" }).then(function(r) {
|
|
11921
|
+
return r.text();
|
|
11922
|
+
}).then(function(t) {
|
|
11923
|
+
ACT_RE.lastIndex = 0;
|
|
11924
|
+
var x;
|
|
11925
|
+
while (x = ACT_RE.exec(t)) {
|
|
11926
|
+
found[x[1]] = x[2];
|
|
11927
|
+
acts[x[1]] = x[2];
|
|
11928
|
+
}
|
|
11929
|
+
}, function() {
|
|
11930
|
+
});
|
|
11931
|
+
})).then(function() {
|
|
11932
|
+
persistSoon();
|
|
11933
|
+
return found;
|
|
11934
|
+
});
|
|
11935
|
+
return actScan;
|
|
11936
|
+
}
|
|
11937
|
+
function actionLabel(id) {
|
|
11938
|
+
return acts[id] || id.slice(0, 8);
|
|
11939
|
+
}
|
|
11693
11940
|
function abs(u, base) {
|
|
11694
11941
|
try {
|
|
11695
11942
|
return new URL(String(u), base || location.href);
|
|
@@ -11753,6 +12000,13 @@ function pageKit(opts) {
|
|
|
11753
12000
|
} catch (e) {
|
|
11754
12001
|
}
|
|
11755
12002
|
}
|
|
12003
|
+
if (isFlight(ct)) {
|
|
12004
|
+
learnParams(text);
|
|
12005
|
+
try {
|
|
12006
|
+
return { rsc: shape(flightData(parseFlight(text))) };
|
|
12007
|
+
} catch (e) {
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
11756
12010
|
return { contentType: (ct || "").split(";")[0], bytes: text.length };
|
|
11757
12011
|
}
|
|
11758
12012
|
function cap(t) {
|
|
@@ -11761,7 +12015,7 @@ function pageKit(opts) {
|
|
|
11761
12015
|
var persistTimer = null;
|
|
11762
12016
|
function persist() {
|
|
11763
12017
|
try {
|
|
11764
|
-
ss.setItem(KEY, JSON.stringify({ v: VERSION, m, po, budget }));
|
|
12018
|
+
ss.setItem(KEY, JSON.stringify({ v: VERSION, m, po, budget, authSrc, acts }));
|
|
11765
12019
|
} catch (e) {
|
|
11766
12020
|
}
|
|
11767
12021
|
}
|
|
@@ -11772,13 +12026,105 @@ function pageKit(opts) {
|
|
|
11772
12026
|
persist();
|
|
11773
12027
|
}, 300);
|
|
11774
12028
|
}
|
|
12029
|
+
function findIn(st, from, needle) {
|
|
12030
|
+
if (!st) return null;
|
|
12031
|
+
for (var i = 0; i < st.length; i++) {
|
|
12032
|
+
var k = st.key(i);
|
|
12033
|
+
if (!k || k.indexOf("__ifk") === 0) continue;
|
|
12034
|
+
var v = st.getItem(k);
|
|
12035
|
+
if (!v || v.indexOf(needle) < 0) continue;
|
|
12036
|
+
if (v === needle) return { from, key: k };
|
|
12037
|
+
try {
|
|
12038
|
+
var j = JSON.parse(v);
|
|
12039
|
+
if (j === needle) return { from, key: k, json: true };
|
|
12040
|
+
var path15 = findPath(j, needle, 0);
|
|
12041
|
+
if (path15) return { from, key: k, path: path15 };
|
|
12042
|
+
} catch (e) {
|
|
12043
|
+
}
|
|
12044
|
+
}
|
|
12045
|
+
return null;
|
|
12046
|
+
}
|
|
12047
|
+
function findPath(o, needle, d) {
|
|
12048
|
+
if (!o || typeof o !== "object" || d > 4) return null;
|
|
12049
|
+
for (var k in o) {
|
|
12050
|
+
if (o[k] === needle) return [k];
|
|
12051
|
+
var p = findPath(o[k], needle, d + 1);
|
|
12052
|
+
if (p) return [k].concat(p);
|
|
12053
|
+
}
|
|
12054
|
+
return null;
|
|
12055
|
+
}
|
|
12056
|
+
function sourceOf(value) {
|
|
12057
|
+
var pre = (/^(bearer|token|basic|jwt)\s+/i.exec(value) || [""])[0];
|
|
12058
|
+
var raw = value.slice(pre.length);
|
|
12059
|
+
if (raw.length < 8) return null;
|
|
12060
|
+
var hit = findIn(lstore(), "localStorage", raw) || findIn(ss, "sessionStorage", raw);
|
|
12061
|
+
if (!hit) {
|
|
12062
|
+
try {
|
|
12063
|
+
document.cookie.split(/;\s*/).forEach(function(c) {
|
|
12064
|
+
var i = c.indexOf("=");
|
|
12065
|
+
if (!hit && i > 0 && decodeURIComponent(c.slice(i + 1)) === raw) hit = { from: "cookie", key: c.slice(0, i) };
|
|
12066
|
+
});
|
|
12067
|
+
} catch (e) {
|
|
12068
|
+
}
|
|
12069
|
+
}
|
|
12070
|
+
hit = hit || { from: "memory" };
|
|
12071
|
+
if (pre) hit.prefix = pre;
|
|
12072
|
+
return hit;
|
|
12073
|
+
}
|
|
12074
|
+
function learnAuth(name, value, u) {
|
|
12075
|
+
if (typeof value !== "string" || name === "cookie" || u.origin !== location.origin) return;
|
|
12076
|
+
authMem[name] = value;
|
|
12077
|
+
var cur = authSrc[name];
|
|
12078
|
+
if (!cur || cur.from === "memory") {
|
|
12079
|
+
var s = sourceOf(value);
|
|
12080
|
+
if (s) authSrc[name] = s;
|
|
12081
|
+
}
|
|
12082
|
+
}
|
|
12083
|
+
function readAuth() {
|
|
12084
|
+
var out = {};
|
|
12085
|
+
Object.keys(authSrc).forEach(function(name) {
|
|
12086
|
+
var s = authSrc[name], v = null;
|
|
12087
|
+
try {
|
|
12088
|
+
if (s.from === "localStorage" || s.from === "sessionStorage") {
|
|
12089
|
+
var st = s.from === "localStorage" ? lstore() : ss;
|
|
12090
|
+
v = st && st.getItem(s.key);
|
|
12091
|
+
if (v != null && (s.json || s.path)) {
|
|
12092
|
+
v = JSON.parse(v);
|
|
12093
|
+
(s.path || []).forEach(function(k) {
|
|
12094
|
+
v = v == null ? v : v[k];
|
|
12095
|
+
});
|
|
12096
|
+
}
|
|
12097
|
+
} else if (s.from === "cookie") {
|
|
12098
|
+
document.cookie.split(/;\s*/).forEach(function(c) {
|
|
12099
|
+
if (c.indexOf(s.key + "=") === 0) v = decodeURIComponent(c.slice(s.key.length + 1));
|
|
12100
|
+
});
|
|
12101
|
+
}
|
|
12102
|
+
} catch (e) {
|
|
12103
|
+
v = null;
|
|
12104
|
+
}
|
|
12105
|
+
if (typeof v !== "string" || !v) v = authMem[name] ? authMem[name].slice((s.prefix || "").length) : null;
|
|
12106
|
+
if (v) out[name] = (s.prefix || "") + v;
|
|
12107
|
+
});
|
|
12108
|
+
return out;
|
|
12109
|
+
}
|
|
11775
12110
|
function rec(kind, method, url, headers, body, status, ct, text, base) {
|
|
11776
12111
|
try {
|
|
11777
12112
|
var u = abs(url, base);
|
|
11778
12113
|
if (!u || !/^https?:$/.test(u.protocol)) return;
|
|
11779
12114
|
if (hostRe && !hostRe.test(u.host)) return;
|
|
11780
|
-
|
|
12115
|
+
scanNext();
|
|
12116
|
+
if (text !== void 0 && isFlight(ct)) learnParams(text);
|
|
12117
|
+
learnFromBody(body, u);
|
|
12118
|
+
var act = null;
|
|
12119
|
+
Object.keys(headers || {}).forEach(function(h) {
|
|
12120
|
+
if (h.toLowerCase() === "next-action" && typeof headers[h] === "string") act = headers[h];
|
|
12121
|
+
});
|
|
12122
|
+
var k = String(method || "GET").toUpperCase() + " " + u.origin + tpl(u.pathname) + (act ? "#" + actionLabel(act) : "");
|
|
11781
12123
|
var e = m[k] || (m[k] = { kind, n: 0, status: [], query: [], headers: [], auth: [] });
|
|
12124
|
+
if (act) {
|
|
12125
|
+
e.next = { action: act };
|
|
12126
|
+
if (!acts[act]) scanActions(true);
|
|
12127
|
+
}
|
|
11782
12128
|
e.n++;
|
|
11783
12129
|
if (status != null && e.status.indexOf(status) < 0) e.status.push(status);
|
|
11784
12130
|
u.searchParams.forEach(function(_, q) {
|
|
@@ -11789,6 +12135,7 @@ function pageKit(opts) {
|
|
|
11789
12135
|
var l = h.toLowerCase();
|
|
11790
12136
|
if (AUTH_H.test(l)) {
|
|
11791
12137
|
if (e.auth.indexOf(l) < 0) e.auth.push(l);
|
|
12138
|
+
learnAuth(l, headers[h], u);
|
|
11792
12139
|
} else if (!NOISE_H.test(l) && !NOISE_H_PREFIX.test(l) && e.headers.indexOf(l) < 0) e.headers.push(l);
|
|
11793
12140
|
});
|
|
11794
12141
|
if (body !== void 0 && !e.req) e.req = bodyShape(body);
|
|
@@ -11837,19 +12184,19 @@ function pageKit(opts) {
|
|
|
11837
12184
|
var url = isReq ? input.url : input;
|
|
11838
12185
|
var hs = {};
|
|
11839
12186
|
var src = init && init.headers || isReq && input.headers;
|
|
11840
|
-
if (src) new win.Headers(src).forEach(function(
|
|
11841
|
-
hs[k] = 1;
|
|
12187
|
+
if (src) new win.Headers(src).forEach(function(v, k) {
|
|
12188
|
+
hs[k] = AUTH_H.test(k) || /^next-action$/i.test(k) ? v : 1;
|
|
11842
12189
|
});
|
|
11843
12190
|
var b = init && init.body;
|
|
11844
12191
|
var at = base();
|
|
11845
|
-
p.then(function(
|
|
12192
|
+
p.then(function(res2) {
|
|
11846
12193
|
try {
|
|
11847
|
-
var ct =
|
|
11848
|
-
if (/event-stream|octet|video|audio|image/i.test(ct || "")) return rec("fetch", method, url, hs, b,
|
|
11849
|
-
|
|
11850
|
-
rec("fetch", method, url, hs, b,
|
|
12194
|
+
var ct = res2.headers.get("content-type");
|
|
12195
|
+
if (/event-stream|octet|video|audio|image/i.test(ct || "")) return rec("fetch", method, url, hs, b, res2.status, ct, void 0, at);
|
|
12196
|
+
res2.clone().text().then(function(t) {
|
|
12197
|
+
rec("fetch", method, url, hs, b, res2.status, ct, cap(t), at);
|
|
11851
12198
|
}, function() {
|
|
11852
|
-
rec("fetch", method, url, hs, b,
|
|
12199
|
+
rec("fetch", method, url, hs, b, res2.status, ct, void 0, at);
|
|
11853
12200
|
});
|
|
11854
12201
|
} catch (e) {
|
|
11855
12202
|
}
|
|
@@ -11872,9 +12219,9 @@ function pageKit(opts) {
|
|
|
11872
12219
|
};
|
|
11873
12220
|
});
|
|
11874
12221
|
patch(X, "setRequestHeader", function(oSet) {
|
|
11875
|
-
return function(k) {
|
|
12222
|
+
return function(k, v) {
|
|
11876
12223
|
try {
|
|
11877
|
-
if (this.__ifkI) this.__ifkI.h[k] = 1;
|
|
12224
|
+
if (this.__ifkI) this.__ifkI.h[k] = AUTH_H.test(k) || /^next-action$/i.test(k) ? v : 1;
|
|
11878
12225
|
} catch (e) {
|
|
11879
12226
|
}
|
|
11880
12227
|
return oSet.apply(this, arguments);
|
|
@@ -12010,10 +12357,43 @@ function pageKit(opts) {
|
|
|
12010
12357
|
} catch (e) {
|
|
12011
12358
|
}
|
|
12012
12359
|
w.addEventListener("pagehide", persist);
|
|
12360
|
+
function retpl(s) {
|
|
12361
|
+
var x = /^(https?:\/\/[^\/]+)(\/.*)?$/.exec(s);
|
|
12362
|
+
return x ? x[1] + tpl(x[2] || "/") : s;
|
|
12363
|
+
}
|
|
12364
|
+
function rekey() {
|
|
12365
|
+
scanNext();
|
|
12366
|
+
var m2 = {};
|
|
12367
|
+
Object.keys(m).forEach(function(k) {
|
|
12368
|
+
var e = m[k], sp = k.indexOf(" "), rest = k.slice(sp + 1).split("#")[0];
|
|
12369
|
+
var nk = k.slice(0, sp) + " " + retpl(rest) + (e.next ? "#" + actionLabel(e.next.action) : "");
|
|
12370
|
+
if (e.next && acts[e.next.action]) e.next.name = acts[e.next.action];
|
|
12371
|
+
var p = m2[nk];
|
|
12372
|
+
if (!p) {
|
|
12373
|
+
m2[nk] = e;
|
|
12374
|
+
return;
|
|
12375
|
+
}
|
|
12376
|
+
p.n += e.n;
|
|
12377
|
+
["status", "query", "headers", "auth"].forEach(function(f) {
|
|
12378
|
+
(e[f] || []).forEach(function(x) {
|
|
12379
|
+
if (p[f].indexOf(x) < 0) p[f].push(x);
|
|
12380
|
+
});
|
|
12381
|
+
});
|
|
12382
|
+
if (!p.req) p.req = e.req;
|
|
12383
|
+
if (!p.res) p.res = e.res;
|
|
12384
|
+
});
|
|
12385
|
+
m = m2;
|
|
12386
|
+
var po2 = {};
|
|
12387
|
+
Object.keys(po).forEach(function(k) {
|
|
12388
|
+
po2[retpl(k)] = po[k];
|
|
12389
|
+
});
|
|
12390
|
+
po = po2;
|
|
12391
|
+
}
|
|
12013
12392
|
function report() {
|
|
12393
|
+
rekey();
|
|
12014
12394
|
var hooked = {};
|
|
12015
12395
|
Object.keys(m).forEach(function(k) {
|
|
12016
|
-
hooked[k.slice(k.indexOf(" ") + 1)] = 1;
|
|
12396
|
+
hooked[k.slice(k.indexOf(" ") + 1).split("#")[0]] = 1;
|
|
12017
12397
|
});
|
|
12018
12398
|
var pathOnly = Object.keys(po).filter(function(p) {
|
|
12019
12399
|
return !hooked[p];
|
|
@@ -12034,6 +12414,9 @@ function pageKit(opts) {
|
|
|
12034
12414
|
complete: false,
|
|
12035
12415
|
blindSpots: ["HttpOnly cookies", "Set-Cookie", "browser-added headers", "worker traffic", "document POST responses", "redirect hops", "cross-origin iframes"]
|
|
12036
12416
|
},
|
|
12417
|
+
// What a replay needs besides the browser's own cookies: header names and
|
|
12418
|
+
// where the page keeps their values. Never the values.
|
|
12419
|
+
auth: { cookies: "browser session (sent automatically, HttpOnly included)", headers: authSrc },
|
|
12037
12420
|
endpoints: m,
|
|
12038
12421
|
pathOnly
|
|
12039
12422
|
};
|
|
@@ -12045,6 +12428,7 @@ function pageKit(opts) {
|
|
|
12045
12428
|
}
|
|
12046
12429
|
w.__ifk = {
|
|
12047
12430
|
version: VERSION,
|
|
12431
|
+
build: BUILD,
|
|
12048
12432
|
mode,
|
|
12049
12433
|
summary: function() {
|
|
12050
12434
|
var c = report().coverage;
|
|
@@ -12089,6 +12473,22 @@ function pageKit(opts) {
|
|
|
12089
12473
|
}
|
|
12090
12474
|
budget.calls++;
|
|
12091
12475
|
var init = { method, credentials: "include", headers: o.headers || {} };
|
|
12476
|
+
if ((o.rsc || /[?&]_rsc=/.test(String(path15))) && !Object.keys(init.headers).some(function(k) {
|
|
12477
|
+
return k.toLowerCase() === "rsc";
|
|
12478
|
+
})) init.headers.rsc = "1";
|
|
12479
|
+
var used = [];
|
|
12480
|
+
if (u.origin === location.origin && o.auth !== false) {
|
|
12481
|
+
var a = readAuth();
|
|
12482
|
+
Object.keys(a).forEach(function(h) {
|
|
12483
|
+
var has = Object.keys(init.headers).some(function(k) {
|
|
12484
|
+
return k.toLowerCase() === h;
|
|
12485
|
+
});
|
|
12486
|
+
if (!has) {
|
|
12487
|
+
init.headers[h] = a[h];
|
|
12488
|
+
used.push(h);
|
|
12489
|
+
}
|
|
12490
|
+
});
|
|
12491
|
+
}
|
|
12092
12492
|
if (o.json !== void 0) {
|
|
12093
12493
|
init.body = JSON.stringify(o.json);
|
|
12094
12494
|
init.headers["content-type"] = "application/json";
|
|
@@ -12097,13 +12497,33 @@ function pageKit(opts) {
|
|
|
12097
12497
|
return origFetch.call(w, u.href, init).then(function(r) {
|
|
12098
12498
|
return r.text().then(function(t) {
|
|
12099
12499
|
var out = { status: r.status };
|
|
12500
|
+
if (used.length) out.auth = used;
|
|
12501
|
+
if (r.status === 401) {
|
|
12502
|
+
var missing = Object.keys(authSrc).filter(function(h) {
|
|
12503
|
+
return used.indexOf(h) < 0;
|
|
12504
|
+
});
|
|
12505
|
+
out.hint = missing.length ? "no value for " + missing.join(", ") + ": use the site once so it sends its token, then retry" : Object.keys(authSrc).length ? "auth headers were sent; the session may have expired" : "no auth header seen yet: use the site once while the kit records";
|
|
12506
|
+
}
|
|
12100
12507
|
var wall = looksLikeWall(r.status, t);
|
|
12101
12508
|
if (wall) {
|
|
12102
12509
|
budget.stopped = wall;
|
|
12103
12510
|
out.stopped = wall;
|
|
12104
12511
|
}
|
|
12105
12512
|
persist();
|
|
12106
|
-
|
|
12513
|
+
var ct = r.headers.get("content-type");
|
|
12514
|
+
if (isFlight(ct)) {
|
|
12515
|
+
learnParams(t);
|
|
12516
|
+
var data = flightData(parseFlight(t));
|
|
12517
|
+
if (data.action !== void 0) data = data.action;
|
|
12518
|
+
var size = JSON.stringify(data === void 0 ? null : data).length;
|
|
12519
|
+
if (size > maxBytes && !o.full) {
|
|
12520
|
+
out.shape = shape(data);
|
|
12521
|
+
out.bytes = size;
|
|
12522
|
+
out.hint = "big result, shown as its shape. Pass {full:true} and pick fields in the same evaluate: .then(r => r.data...)";
|
|
12523
|
+
} else out.data = data;
|
|
12524
|
+
return out;
|
|
12525
|
+
}
|
|
12526
|
+
if (t.length > maxBytes && !o.full) {
|
|
12107
12527
|
out.truncated = t.length;
|
|
12108
12528
|
t = t.slice(0, maxBytes);
|
|
12109
12529
|
}
|
|
@@ -12111,6 +12531,7 @@ function pageKit(opts) {
|
|
|
12111
12531
|
out.data = JSON.parse(t);
|
|
12112
12532
|
} catch (e) {
|
|
12113
12533
|
out.text = t.slice(0, 2e3);
|
|
12534
|
+
if (/html/i.test(ct || "")) out.hint = "got HTML, not data. On Next.js pages pass {rsc:true}";
|
|
12114
12535
|
}
|
|
12115
12536
|
return out;
|
|
12116
12537
|
});
|
|
@@ -12118,6 +12539,37 @@ function pageKit(opts) {
|
|
|
12118
12539
|
return { error: String(e && e.message || e) };
|
|
12119
12540
|
});
|
|
12120
12541
|
},
|
|
12542
|
+
// Calls a Next.js Server Action by export name (or id). The id is looked up
|
|
12543
|
+
// in the current page's chunks first, so it survives deploys.
|
|
12544
|
+
action: function(nameOrId, args2, o) {
|
|
12545
|
+
o = o || {};
|
|
12546
|
+
if (budget.stopped) return Promise.resolve({ error: "stopped", reason: budget.stopped });
|
|
12547
|
+
if (!o.approved) return Promise.resolve({ error: "write needs approval", hint: "a Server Action runs server code and may change data: ask the user, then pass {approved:true}" });
|
|
12548
|
+
var self = this;
|
|
12549
|
+
return scanActions(true).then(function(found) {
|
|
12550
|
+
var id = null;
|
|
12551
|
+
Object.keys(found).forEach(function(k) {
|
|
12552
|
+
if (found[k] === nameOrId || k === nameOrId) id = k;
|
|
12553
|
+
});
|
|
12554
|
+
if (!id) Object.keys(acts).forEach(function(k) {
|
|
12555
|
+
if (acts[k] === nameOrId) id = k;
|
|
12556
|
+
});
|
|
12557
|
+
if (!id && /^[0-9a-f]{40,44}$/.test(nameOrId)) id = nameOrId;
|
|
12558
|
+
if (!id) return { error: "action not found", hint: "open a page that uses " + nameOrId + ", re-run the install eval, then retry" };
|
|
12559
|
+
var body = JSON.stringify(Array.isArray(args2) ? args2 : args2 === void 0 ? [] : [args2]);
|
|
12560
|
+
return self.call("POST", o.path || location.pathname + location.search, {
|
|
12561
|
+
approved: true,
|
|
12562
|
+
full: o.full,
|
|
12563
|
+
maxBytes: o.maxBytes,
|
|
12564
|
+
headers: { "next-action": id, accept: "text/x-component", "content-type": "text/plain;charset=UTF-8" },
|
|
12565
|
+
body
|
|
12566
|
+
}).then(function(r) {
|
|
12567
|
+
if (r && r.status === 404) r.hint = "Server Action id not accepted (new deploy?): reload the page, re-run the install eval, retry";
|
|
12568
|
+
if (r) r.actionId = id.slice(0, 8);
|
|
12569
|
+
return r;
|
|
12570
|
+
});
|
|
12571
|
+
});
|
|
12572
|
+
},
|
|
12121
12573
|
// Removes every hook and forgets the capture.
|
|
12122
12574
|
stop: function() {
|
|
12123
12575
|
restores.forEach(function(r) {
|
|
@@ -12151,7 +12603,12 @@ function pageKit(opts) {
|
|
|
12151
12603
|
}
|
|
12152
12604
|
};
|
|
12153
12605
|
persist();
|
|
12154
|
-
|
|
12606
|
+
scanNext();
|
|
12607
|
+
if (w.__next_f || w.__NEXT_DATA__) scanActions(false);
|
|
12608
|
+
var res = { installed: true, again: false, version: VERSION, build: BUILD, mode, restored };
|
|
12609
|
+
if (replaced) res.replaced = replaced;
|
|
12610
|
+
if (w.__next_f || w.__NEXT_DATA__) res.nextjs = true;
|
|
12611
|
+
return res;
|
|
12155
12612
|
}
|
|
12156
12613
|
var init_page_kit = __esm({
|
|
12157
12614
|
"src/lib/kit/page-kit.ts"() {
|
|
@@ -12223,7 +12680,7 @@ function scanString(s, where, out) {
|
|
|
12223
12680
|
if (TYPE_NAMES.has(s)) return;
|
|
12224
12681
|
for (const [kind, re] of DETECTORS) {
|
|
12225
12682
|
if (kind === "token") {
|
|
12226
|
-
for (const part of
|
|
12683
|
+
for (const part of splitWords(s)) {
|
|
12227
12684
|
if (part.includes("{")) continue;
|
|
12228
12685
|
if (re.test(part)) {
|
|
12229
12686
|
out.push({ where, kind, sample: mask(part) });
|
|
@@ -12254,16 +12711,133 @@ function scanCapture(report) {
|
|
|
12254
12711
|
for (const [key, ep] of Object.entries(report.endpoints)) {
|
|
12255
12712
|
const where = `endpoints["${key.length > 80 ? key.slice(0, 80) + "\u2026" : key}"]`;
|
|
12256
12713
|
scanString(key, where, out);
|
|
12257
|
-
walkValues(ep, where, out);
|
|
12714
|
+
walkValues({ ...ep, next: void 0 }, where, out);
|
|
12258
12715
|
}
|
|
12259
12716
|
report.pathOnly.forEach((p, i) => scanString(p, `pathOnly[${i}]`, out));
|
|
12260
12717
|
return out;
|
|
12261
12718
|
}
|
|
12719
|
+
function splitWords(s) {
|
|
12720
|
+
const m = s.match(/^(?:[A-Z]+ )?https?:\/\/([^/]+)(.*)$/);
|
|
12721
|
+
if (!m) return s.split(/[/\s?&=#:;,]/);
|
|
12722
|
+
return [...m[1].split(/[.:]/), ...m[2].split(/[/\s?&=#:;,]/)];
|
|
12723
|
+
}
|
|
12724
|
+
function redactUrl(s) {
|
|
12725
|
+
const kinds = [];
|
|
12726
|
+
const hit = (part) => {
|
|
12727
|
+
if (!part || part.includes("{")) return null;
|
|
12728
|
+
let d = part;
|
|
12729
|
+
try {
|
|
12730
|
+
d = decodeURIComponent(part);
|
|
12731
|
+
} catch {
|
|
12732
|
+
}
|
|
12733
|
+
for (const [kind, re] of DETECTORS) if (re.test(d)) return kind;
|
|
12734
|
+
return null;
|
|
12735
|
+
};
|
|
12736
|
+
const fixSeg = (seg) => {
|
|
12737
|
+
let found = false;
|
|
12738
|
+
const parts = seg.split(/([^A-Za-z0-9_%@+])/).map((p) => {
|
|
12739
|
+
const kk = hit(p);
|
|
12740
|
+
if (kk) {
|
|
12741
|
+
kinds.push(kk);
|
|
12742
|
+
found = true;
|
|
12743
|
+
return PLACEHOLDER;
|
|
12744
|
+
}
|
|
12745
|
+
return p;
|
|
12746
|
+
});
|
|
12747
|
+
if (found) return parts.join("");
|
|
12748
|
+
const k = hit(seg);
|
|
12749
|
+
if (k) {
|
|
12750
|
+
kinds.push(k);
|
|
12751
|
+
return PLACEHOLDER;
|
|
12752
|
+
}
|
|
12753
|
+
return seg;
|
|
12754
|
+
};
|
|
12755
|
+
const m = s.match(/^((?:[A-Z]+ )?https?:\/\/)([^/]+)(.*)$/);
|
|
12756
|
+
if (!m) return { s: s.split("/").map(fixSeg).join("/"), kinds };
|
|
12757
|
+
const host = m[2].split(".").map((l) => {
|
|
12758
|
+
const k = hit(l);
|
|
12759
|
+
if (k) {
|
|
12760
|
+
kinds.push(k);
|
|
12761
|
+
return "x";
|
|
12762
|
+
}
|
|
12763
|
+
return l;
|
|
12764
|
+
}).join(".");
|
|
12765
|
+
return { s: m[1] + host + m[3].split("/").map(fixSeg).join("/"), kinds };
|
|
12766
|
+
}
|
|
12767
|
+
function redactShape(v, kinds) {
|
|
12768
|
+
if (typeof v === "string") {
|
|
12769
|
+
if (TYPE_NAMES.has(v)) return v;
|
|
12770
|
+
const f = [];
|
|
12771
|
+
scanString(v, "", f);
|
|
12772
|
+
if (f.length) {
|
|
12773
|
+
kinds.push(f[0].kind);
|
|
12774
|
+
return "string";
|
|
12775
|
+
}
|
|
12776
|
+
return v;
|
|
12777
|
+
}
|
|
12778
|
+
if (Array.isArray(v)) return v.map((x) => redactShape(x, kinds));
|
|
12779
|
+
if (v && typeof v === "object") {
|
|
12780
|
+
const o = {};
|
|
12781
|
+
for (const [k, x] of Object.entries(v)) {
|
|
12782
|
+
const f = [];
|
|
12783
|
+
scanString(k, "", f);
|
|
12784
|
+
const key = f.length ? "{key}" : k;
|
|
12785
|
+
if (f.length) kinds.push(f[0].kind);
|
|
12786
|
+
if (!(key in o)) o[key] = redactShape(x, kinds);
|
|
12787
|
+
}
|
|
12788
|
+
return o;
|
|
12789
|
+
}
|
|
12790
|
+
return v;
|
|
12791
|
+
}
|
|
12792
|
+
function redactCapture(report) {
|
|
12793
|
+
const redacted = [];
|
|
12794
|
+
const endpoints = {};
|
|
12795
|
+
for (const [key, ep] of Object.entries(report.endpoints)) {
|
|
12796
|
+
const kinds = [];
|
|
12797
|
+
const { s: newKey, kinds: keyKinds } = redactUrl(key);
|
|
12798
|
+
kinds.push(...keyKinds);
|
|
12799
|
+
const clean = { ...ep };
|
|
12800
|
+
clean.query = union((ep.query || []).map((q) => {
|
|
12801
|
+
const f = [];
|
|
12802
|
+
scanString(q, "", f);
|
|
12803
|
+
if (f.length) kinds.push(f[0].kind);
|
|
12804
|
+
return f.length ? "{key}" : q;
|
|
12805
|
+
}));
|
|
12806
|
+
clean.headers = (ep.headers || []).filter((h) => {
|
|
12807
|
+
const f = [];
|
|
12808
|
+
scanString(h, "", f);
|
|
12809
|
+
if (f.length) kinds.push(f[0].kind);
|
|
12810
|
+
return !f.length;
|
|
12811
|
+
});
|
|
12812
|
+
if (ep.req !== void 0) clean.req = redactShape(ep.req, kinds);
|
|
12813
|
+
if (ep.res !== void 0) clean.res = redactShape(ep.res, kinds);
|
|
12814
|
+
for (const k of new Set(kinds)) redacted.push({ where: newKey, kind: k, sample: "replaced" });
|
|
12815
|
+
if (kinds.length) clean.redacted = union(ep.redacted, kinds);
|
|
12816
|
+
const prev = endpoints[newKey];
|
|
12817
|
+
endpoints[newKey] = prev ? {
|
|
12818
|
+
...prev,
|
|
12819
|
+
n: prev.n + clean.n,
|
|
12820
|
+
status: union(prev.status, clean.status),
|
|
12821
|
+
query: union(prev.query, clean.query),
|
|
12822
|
+
headers: union(prev.headers, clean.headers),
|
|
12823
|
+
auth: union(prev.auth, clean.auth),
|
|
12824
|
+
req: prev.req ?? clean.req,
|
|
12825
|
+
res: prev.res ?? clean.res,
|
|
12826
|
+
...prev.redacted || clean.redacted ? { redacted: union(prev.redacted, clean.redacted) } : {}
|
|
12827
|
+
} : clean;
|
|
12828
|
+
}
|
|
12829
|
+
const pathOnly = union(report.pathOnly.map((p) => {
|
|
12830
|
+
const r = redactUrl(p);
|
|
12831
|
+
for (const k of new Set(r.kinds)) redacted.push({ where: r.s, kind: k, sample: "replaced" });
|
|
12832
|
+
return r.s;
|
|
12833
|
+
}));
|
|
12834
|
+
return { report: { ...report, endpoints, pathOnly }, redacted };
|
|
12835
|
+
}
|
|
12262
12836
|
function splitKey(key) {
|
|
12263
12837
|
const sp = key.indexOf(" ");
|
|
12264
12838
|
const method = key.slice(0, sp);
|
|
12265
12839
|
const rest = key.slice(sp + 1);
|
|
12266
|
-
const m = rest.match(/^(https?:\/\/[^/]+)(\/.*)?$/);
|
|
12840
|
+
const m = rest.split("#")[0].match(/^(https?:\/\/[^/]+)(\/.*)?$/);
|
|
12267
12841
|
return { method, origin: m ? m[1] : "", path: m?.[2] || "/" };
|
|
12268
12842
|
}
|
|
12269
12843
|
function pascal(s) {
|
|
@@ -12284,7 +12858,7 @@ function functionNameFor(method, p, res, kind) {
|
|
|
12284
12858
|
function buildApiMap(report) {
|
|
12285
12859
|
const used = /* @__PURE__ */ new Map();
|
|
12286
12860
|
const endpoints = Object.entries(report.endpoints).map(([key, ep]) => ({ key, ep, ...splitKey(key) })).sort((a, b) => a.path === b.path ? a.method.localeCompare(b.method) : a.path.localeCompare(b.path)).map(({ ep, method, origin, path: p }) => {
|
|
12287
|
-
let fn = functionNameFor(method, p, ep.res, ep.kind);
|
|
12861
|
+
let fn = ep.next?.name || (ep.next ? "action" + (pascal(p.split("/").filter((x) => x && !x.startsWith("{")).slice(-2).join(" ")) || "Root") : functionNameFor(method, p, ep.res, ep.kind));
|
|
12288
12862
|
const n = used.get(fn) ?? 0;
|
|
12289
12863
|
used.set(fn, n + 1);
|
|
12290
12864
|
if (n) fn += n + 1;
|
|
@@ -12300,18 +12874,42 @@ function buildApiMap(report) {
|
|
|
12300
12874
|
authHeaders: ep.auth,
|
|
12301
12875
|
request: ep.req,
|
|
12302
12876
|
response: ep.res,
|
|
12303
|
-
functionName: fn
|
|
12877
|
+
functionName: fn,
|
|
12878
|
+
...ep.redacted?.length ? { privateValues: ep.redacted } : {},
|
|
12879
|
+
...(ep.headers || []).includes("rsc") || (ep.query || []).includes("_rsc") ? { rsc: true } : {},
|
|
12880
|
+
...ep.next ? { action: { name: ep.next.name, id: ep.next.action } } : {}
|
|
12304
12881
|
};
|
|
12305
12882
|
});
|
|
12306
12883
|
return {
|
|
12307
12884
|
origin: report.origin,
|
|
12308
12885
|
capturedAt: report.capturedAt,
|
|
12309
12886
|
coverage: report.coverage,
|
|
12887
|
+
identity: {
|
|
12888
|
+
cookies: report.auth?.cookies ?? "browser session (sent automatically, HttpOnly included)",
|
|
12889
|
+
headers: Object.entries(report.auth?.headers ?? {}).map(([name, src]) => ({ name, source: describeAuthSource(src) }))
|
|
12890
|
+
},
|
|
12310
12891
|
replay: "in-page only: load the kit, then the client, and call window.__ifkApi.<fn>() through the agent's evaluate tool. HttpOnly cookies never leave the browser, so the same calls fail from curl.",
|
|
12311
12892
|
endpoints,
|
|
12312
12893
|
pathOnly: report.pathOnly
|
|
12313
12894
|
};
|
|
12314
12895
|
}
|
|
12896
|
+
function describeAuthSource(s) {
|
|
12897
|
+
const pre = s.prefix ? ` with "${s.prefix.trim()} " prefix` : "";
|
|
12898
|
+
if (s.from === "memory") return `kept in page memory${pre}; __ifk.call reuses it after the site sends it once`;
|
|
12899
|
+
const at = s.path?.length ? `${s.key} -> ${s.path.join(".")}` : s.key;
|
|
12900
|
+
return `${s.from}["${at}"]${pre}; __ifk.call attaches it automatically`;
|
|
12901
|
+
}
|
|
12902
|
+
function pathParams(p) {
|
|
12903
|
+
const used = /* @__PURE__ */ new Set();
|
|
12904
|
+
return (p.match(/\{([^}]+)\}/g) || []).map((m) => {
|
|
12905
|
+
let base = m.slice(1, -1).replace(/[^A-Za-z0-9_$]/g, "_");
|
|
12906
|
+
if (!/^[A-Za-z_$]/.test(base)) base = "p_" + base;
|
|
12907
|
+
let name = base, n = 2;
|
|
12908
|
+
while (used.has(name)) name = `${base}${n++}`;
|
|
12909
|
+
used.add(name);
|
|
12910
|
+
return name;
|
|
12911
|
+
});
|
|
12912
|
+
}
|
|
12315
12913
|
function takesBody(ep) {
|
|
12316
12914
|
return ep.request !== void 0 || ep.method === "POST" || ep.method === "PUT" || ep.method === "PATCH";
|
|
12317
12915
|
}
|
|
@@ -12327,6 +12925,10 @@ function renderClient(map) {
|
|
|
12327
12925
|
lines.push(` if (!window.__ifk) return Promise.resolve({ error: "NOT_INSTALLED: eval(sessionStorage.__ifk) first" });`);
|
|
12328
12926
|
lines.push(` return window.__ifk.call(m, p, o);`);
|
|
12329
12927
|
lines.push(` }`);
|
|
12928
|
+
lines.push(` function a(name, args, o) {`);
|
|
12929
|
+
lines.push(` if (!window.__ifk) return Promise.resolve({ error: "NOT_INSTALLED: eval(sessionStorage.__ifk) first" });`);
|
|
12930
|
+
lines.push(` return window.__ifk.action(name, args, o);`);
|
|
12931
|
+
lines.push(` }`);
|
|
12330
12932
|
lines.push(` function q(query) {`);
|
|
12331
12933
|
lines.push(` if (!query) return "";`);
|
|
12332
12934
|
lines.push(` var s = new URLSearchParams(query).toString();`);
|
|
@@ -12342,17 +12944,20 @@ function renderClient(map) {
|
|
|
12342
12944
|
skipped.push(`${ep.method} ${ep.origin}${ep.path} (cross-origin)`);
|
|
12343
12945
|
continue;
|
|
12344
12946
|
}
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12947
|
+
if (ep.action) {
|
|
12948
|
+
const ref = JSON.stringify(ep.action.name || ep.action.id);
|
|
12949
|
+
lines.push(` // Next.js Server Action at ${ep.path}. args: array of the action's arguments. Needs {approved:true}.`);
|
|
12950
|
+
lines.push(` ${ep.functionName}: function (args, opts) { return a(${ref}, args, opts); },`);
|
|
12951
|
+
continue;
|
|
12952
|
+
}
|
|
12953
|
+
const params = pathParams(ep.path);
|
|
12954
|
+
let i = 0;
|
|
12955
|
+
const urlExpr = JSON.stringify(ep.path).replace(/\{[^}]+\}/g, () => `" + encodeURIComponent(${params[i++]}) + "`).replace(/ \+ ""$/, "").replace(/^"" \+ /, "");
|
|
12352
12956
|
const hasBody = takesBody(ep);
|
|
12353
12957
|
if (hasBody) params.push("body");
|
|
12354
12958
|
params.push("query", "opts");
|
|
12355
|
-
const
|
|
12959
|
+
const base = [hasBody ? "json: body" : "", ep.rsc ? "rsc: true" : ""].filter(Boolean).join(", ");
|
|
12960
|
+
const optsExpr = base ? `Object.assign({ ${base} }, opts)` : `opts`;
|
|
12356
12961
|
const doc = [
|
|
12357
12962
|
ep.query.length ? `query: ${ep.query.join(", ")}` : "",
|
|
12358
12963
|
ep.status.length ? `seen ${ep.status.join("/")}` : ""
|
|
@@ -12387,11 +12992,18 @@ ${indent}}`;
|
|
|
12387
12992
|
function renderTypes(map) {
|
|
12388
12993
|
const out = [`// Generated by \`iframer ingest\` from ${map.origin}. Shapes come from one sample each: fields may be optional.`];
|
|
12389
12994
|
out.push(`type IfkResult<T> = Promise<{ status?: number; data?: T; text?: string; truncated?: number; error?: string; stopped?: string }>;`);
|
|
12390
|
-
out.push(`type IfkOpts = { approved?: boolean; headers?: Record<string, string>; maxBytes?: number };`);
|
|
12995
|
+
out.push(`type IfkOpts = { approved?: boolean; headers?: Record<string, string>; maxBytes?: number; full?: boolean; rsc?: boolean; auth?: boolean };`);
|
|
12391
12996
|
out.push(`interface IfkApi {`);
|
|
12392
12997
|
for (const ep of map.endpoints) {
|
|
12393
12998
|
if (ep.kind === "ws" || ep.kind === "sse" || ep.kind === "beacon" || ep.origin !== map.origin) continue;
|
|
12394
|
-
|
|
12999
|
+
if (ep.action) {
|
|
13000
|
+
const args2 = ep.request?.json !== void 0 ? tsType(ep.request.json, " ") : "unknown[]";
|
|
13001
|
+
const r = ep.response?.rsc?.action;
|
|
13002
|
+
out.push(` /** Next.js Server Action ${ep.action.name || ep.action.id.slice(0, 8)} at ${ep.path} */`);
|
|
13003
|
+
out.push(` ${ep.functionName}(args: ${args2}, opts?: IfkOpts): IfkResult<${r !== void 0 ? tsType(r, " ") : "unknown"}>;`);
|
|
13004
|
+
continue;
|
|
13005
|
+
}
|
|
13006
|
+
const ids = pathParams(ep.path).map((n) => `${n}: string | number`);
|
|
12395
13007
|
const hasBody = takesBody(ep);
|
|
12396
13008
|
const req = ep.request?.json !== void 0 ? tsType(ep.request.json, " ") : ep.request?.form || ep.request?.multipart ? "Record<string, string>" : "unknown";
|
|
12397
13009
|
const res = ep.response && typeof ep.response === "object" && !Array.isArray(ep.response) && "contentType" in ep.response && "bytes" in ep.response ? "unknown" : tsType(ep.response, " ");
|
|
@@ -12415,14 +13027,14 @@ function shortShape(s, depth = 0) {
|
|
|
12415
13027
|
return "?";
|
|
12416
13028
|
}
|
|
12417
13029
|
function describeEndpoint(ep) {
|
|
12418
|
-
const parts = [ep.kind, ep.status.length ? `status ${ep.status.join("/")}` : ""];
|
|
13030
|
+
const parts = [ep.action ? `Next.js Server Action ${ep.action.name || ep.action.id.slice(0, 8)}` : ep.kind, ep.rsc ? "RSC (page data)" : "", ep.status.length ? `status ${ep.status.join("/")}` : ""];
|
|
12419
13031
|
if (ep.request?.json !== void 0) parts.push(`body ${shortShape(ep.request.json)}`);
|
|
12420
13032
|
else if (ep.request?.form) parts.push(`form ${Object.keys(ep.request.form).join(",")}`);
|
|
12421
13033
|
if (ep.response !== void 0) parts.push(`returns ${shortShape(ep.response)}`);
|
|
12422
13034
|
if (ep.authHeaders.length) parts.push(`auth ${ep.authHeaders.join(",")}`);
|
|
12423
13035
|
return parts.filter(Boolean).join("; ");
|
|
12424
13036
|
}
|
|
12425
|
-
var import_fs11, import_os4, import_path12, CaptureError, TYPE_NAMES, DETECTORS, VERB;
|
|
13037
|
+
var import_fs11, import_os4, import_path12, CaptureError, TYPE_NAMES, DETECTORS, PLACEHOLDER, union, VERB;
|
|
12426
13038
|
var init_capture = __esm({
|
|
12427
13039
|
"src/lib/kit/capture.ts"() {
|
|
12428
13040
|
"use strict";
|
|
@@ -12439,6 +13051,8 @@ var init_capture = __esm({
|
|
|
12439
13051
|
["phone", /\+?\d[\d ().-]{8,}\d/],
|
|
12440
13052
|
["token", /(?=[A-Za-z0-9_\-.]*\d)(?=[A-Za-z0-9_\-.]*[A-Za-z])^[A-Za-z0-9_\-.]{24,}$/]
|
|
12441
13053
|
];
|
|
13054
|
+
PLACEHOLDER = "{id}";
|
|
13055
|
+
union = (a = [], b = []) => [.../* @__PURE__ */ new Set([...a, ...b])];
|
|
12442
13056
|
VERB = { GET: "get", HEAD: "head", POST: "create", PUT: "update", PATCH: "update", DELETE: "delete" };
|
|
12443
13057
|
}
|
|
12444
13058
|
});
|
|
@@ -12449,6 +13063,7 @@ __export(kit_exports, {
|
|
|
12449
13063
|
CaptureError: () => CaptureError,
|
|
12450
13064
|
KIT_TARGETS: () => KIT_TARGETS,
|
|
12451
13065
|
buildApiMap: () => buildApiMap,
|
|
13066
|
+
describeAuthSource: () => describeAuthSource,
|
|
12452
13067
|
describeEndpoint: () => describeEndpoint,
|
|
12453
13068
|
downloadDirs: () => downloadDirs,
|
|
12454
13069
|
findLatestCapture: () => findLatestCapture,
|
|
@@ -12456,6 +13071,7 @@ __export(kit_exports, {
|
|
|
12456
13071
|
kitInstructions: () => kitInstructions,
|
|
12457
13072
|
kitSource: () => kitSource,
|
|
12458
13073
|
parseCapture: () => parseCapture,
|
|
13074
|
+
redactCapture: () => redactCapture,
|
|
12459
13075
|
renderClient: () => renderClient,
|
|
12460
13076
|
renderTypes: () => renderTypes,
|
|
12461
13077
|
scanCapture: () => scanCapture,
|
|
@@ -12467,7 +13083,9 @@ function kitSource(opts = {}) {
|
|
|
12467
13083
|
if (opts.hostFilter) clean.hostFilter = opts.hostFilter;
|
|
12468
13084
|
if (opts.maxBody) clean.maxBody = opts.maxBody;
|
|
12469
13085
|
if (opts.maxCalls) clean.maxCalls = opts.maxCalls;
|
|
12470
|
-
|
|
13086
|
+
const fn = pageKit.toString();
|
|
13087
|
+
const build = import_crypto13.default.createHash("sha1").update(fn).digest("hex").slice(0, 10);
|
|
13088
|
+
return `(${fn})(${JSON.stringify({ ...clean, build })});
|
|
12471
13089
|
`;
|
|
12472
13090
|
}
|
|
12473
13091
|
function kitDir() {
|
|
@@ -12484,6 +13102,7 @@ function kitInstructions(target, kitPath, clientPath) {
|
|
|
12484
13102
|
`Rules:`,
|
|
12485
13103
|
`- After EVERY full page load, run \`${INSTALL}\` again. It restores what was captured. Check with \`${STATUS}\`.`,
|
|
12486
13104
|
`- Drive the site with your normal tools while it records. Only shapes are kept, never values.`,
|
|
13105
|
+
`- Next.js sites: page data comes back from \`window.__ifk.call('GET','/path',{rsc:true})\` as {components:{Name:props}}. Server Actions: \`window.__ifk.action('exportName',[args],{approved:true})\` (the id is looked up again after deploys).`,
|
|
12487
13106
|
`- Replay: \`window.__ifk.call('GET','/path')\` runs in the page with the user's cookies. Writes need {approved:true}, so ask the user first. It stops on 403, 429 or a captcha page: do not retry.`,
|
|
12488
13107
|
`- Coverage is never complete: workers, redirect hops, HttpOnly cookies and cross-origin iframes are invisible to the kit.`,
|
|
12489
13108
|
`- Done: \`window.__ifk.stop()\` removes the hooks.`
|
|
@@ -12552,10 +13171,11 @@ function drainFallback() {
|
|
|
12552
13171
|
`until next is null, write the joined chunks to a file, then \`iframer ingest <file>\`.`
|
|
12553
13172
|
].join("\n");
|
|
12554
13173
|
}
|
|
12555
|
-
var import_fs12, import_path13, KIT_TARGETS, MAKE_INPUT, SS, stash, STASH, INSTALL, INSTALL_CLIENT, STATUS;
|
|
13174
|
+
var import_crypto13, import_fs12, import_path13, KIT_TARGETS, MAKE_INPUT, SS, stash, STASH, INSTALL, INSTALL_CLIENT, STATUS;
|
|
12556
13175
|
var init_kit = __esm({
|
|
12557
13176
|
"src/lib/kit/index.ts"() {
|
|
12558
13177
|
"use strict";
|
|
13178
|
+
import_crypto13 = __toESM(require("crypto"));
|
|
12559
13179
|
import_fs12 = __toESM(require("fs"));
|
|
12560
13180
|
import_path13 = __toESM(require("path"));
|
|
12561
13181
|
init_paths();
|
|
@@ -13662,6 +14282,14 @@ Screenshot: ${shot}`);
|
|
|
13662
14282
|
break;
|
|
13663
14283
|
}
|
|
13664
14284
|
// ─── In-page capture kit (any agent browser, T3 Code first) ─────
|
|
14285
|
+
case "refresh": {
|
|
14286
|
+
const kit = await Promise.resolve().then(() => (init_kit(), kit_exports));
|
|
14287
|
+
const kitPath = kit.writeKit({});
|
|
14288
|
+
const skill = installSkill();
|
|
14289
|
+
console.log(` Kit: ${kitPath}`);
|
|
14290
|
+
console.log(skill ? " Skill: ~/.claude/commands/iframer.md" : " Skill: not found in the package, skipped");
|
|
14291
|
+
break;
|
|
14292
|
+
}
|
|
13665
14293
|
case "kit": {
|
|
13666
14294
|
const kit = await Promise.resolve().then(() => (init_kit(), kit_exports));
|
|
13667
14295
|
const target = parseFlag(args, "--for") || "t3";
|
|
@@ -13704,6 +14332,7 @@ Screenshot: ${shot}`);
|
|
|
13704
14332
|
console.error(" --typed Also write client.d.ts");
|
|
13705
14333
|
console.error(" --keep Keep the capture file (default: deleted after ingest)");
|
|
13706
14334
|
console.error(" --no-knowledge Don't update ~/.iframer/knowledge");
|
|
14335
|
+
console.error(" --strict Refuse the capture on any secret-looking value instead of replacing it");
|
|
13707
14336
|
console.error(" --json One-line JSON summary");
|
|
13708
14337
|
process.exit(1);
|
|
13709
14338
|
}
|
|
@@ -13723,6 +14352,8 @@ Screenshot: ${shot}`);
|
|
|
13723
14352
|
console.error(` ${e.message}`);
|
|
13724
14353
|
process.exit(1);
|
|
13725
14354
|
}
|
|
14355
|
+
let redacted = [];
|
|
14356
|
+
if (!hasFlag(args, "--strict")) ({ report, redacted } = kit.redactCapture(report));
|
|
13726
14357
|
const findings = kit.scanCapture(report);
|
|
13727
14358
|
if (findings.length) {
|
|
13728
14359
|
removeInput();
|
|
@@ -13764,16 +14395,23 @@ Screenshot: ${shot}`);
|
|
|
13764
14395
|
})),
|
|
13765
14396
|
notes: [
|
|
13766
14397
|
`In-page capture: ${c2.seen} seen, ${c2.withPayload} with payload, ${c2.pathOnly} path only. Not complete: ${c2.blindSpots.join(", ")}.`,
|
|
13767
|
-
"Replay in the page (window.__ifk.call): HttpOnly cookies never leave the browser."
|
|
14398
|
+
"Replay in the page (window.__ifk.call): HttpOnly cookies never leave the browser.",
|
|
14399
|
+
...map.identity.headers.map((h) => `Auth header ${h.name}: ${h.source}.`),
|
|
14400
|
+
...redacted.length ? [`Private values replaced with placeholders in ${new Set(redacted.map((r) => r.where)).size} endpoint(s); see privateValues in api.json and pass them per call.`] : []
|
|
13768
14401
|
]
|
|
13769
14402
|
});
|
|
13770
14403
|
}
|
|
13771
14404
|
removeInput();
|
|
13772
14405
|
const c = report.coverage;
|
|
13773
14406
|
if (json) {
|
|
13774
|
-
console.log(JSON.stringify({ ok: true, origin: report.origin, endpoints: map.endpoints.length, coverage: { seen: c.seen, withPayload: c.withPayload, pathOnly: c.pathOnly }, files: written, inputDeleted: !fromStdin && !hasFlag(args, "--keep") }));
|
|
14407
|
+
console.log(JSON.stringify({ ok: true, origin: report.origin, endpoints: map.endpoints.length, coverage: { seen: c.seen, withPayload: c.withPayload, pathOnly: c.pathOnly }, redacted: redacted.map((r) => ({ kind: r.kind, at: r.where })), files: written, inputDeleted: !fromStdin && !hasFlag(args, "--keep") }));
|
|
13775
14408
|
} else {
|
|
13776
14409
|
console.log(` ${report.origin}: ${map.endpoints.length} endpoints (${c.withPayload} with payload, ${c.pathOnly} path only). Not complete.`);
|
|
14410
|
+
for (const h of map.identity.headers) console.log(` Identity: ${h.name} <- ${h.source}`);
|
|
14411
|
+
if (redacted.length) {
|
|
14412
|
+
console.log(` Replaced ${redacted.length} secret-looking value(s) with placeholders ({id}, {key}, "string"):`);
|
|
14413
|
+
for (const r of redacted.slice(0, 10)) console.log(` ${r.kind} at ${r.where}`);
|
|
14414
|
+
}
|
|
13777
14415
|
for (const e of map.endpoints.slice(0, 40)) console.log(` ${e.method.padEnd(6)} ${e.origin === map.origin ? e.path : e.origin + e.path} -> ${e.functionName}`);
|
|
13778
14416
|
if (map.endpoints.length > 40) console.log(` \u2026 ${map.endpoints.length - 40} more in api.json`);
|
|
13779
14417
|
console.log(` Wrote: ${written.join(", ")}`);
|
|
@@ -14004,6 +14642,11 @@ Screenshot: ${shot}`);
|
|
|
14004
14642
|
console.error(" sudo npm install -g iframer-toolkit@latest");
|
|
14005
14643
|
process.exit(1);
|
|
14006
14644
|
}
|
|
14645
|
+
try {
|
|
14646
|
+
require("child_process").execSync("iframer refresh", { stdio: "inherit" });
|
|
14647
|
+
} catch {
|
|
14648
|
+
console.error(" Could not refresh the kit and skill. Run: iframer refresh");
|
|
14649
|
+
}
|
|
14007
14650
|
console.log();
|
|
14008
14651
|
const hasExt = extensionInstalled();
|
|
14009
14652
|
await reloadAndRestartServer(hasExt);
|
|
@@ -14082,6 +14725,7 @@ Screenshot: ${shot}`);
|
|
|
14082
14725
|
--out <dir> Output directory (default: ./<host>-api/)
|
|
14083
14726
|
--keep Keep the capture file (default: deleted)
|
|
14084
14727
|
--no-knowledge Don't update ~/.iframer/knowledge
|
|
14728
|
+
--strict Refuse on any secret-looking value instead of replacing it
|
|
14085
14729
|
--json One-line JSON summary
|
|
14086
14730
|
|
|
14087
14731
|
Session:
|
|
@@ -14126,6 +14770,7 @@ Screenshot: ${shot}`);
|
|
|
14126
14770
|
extension path Print the extension folder to load in chrome://extensions
|
|
14127
14771
|
extension token Print the pairing token (only needed to pair by hand)
|
|
14128
14772
|
update Update iframer via npm; reload the extension too if it's installed
|
|
14773
|
+
refresh Rewrite the in-page kit and the /iframer skill from this version (update does it)
|
|
14129
14774
|
update --check Report whether a newer version is available (no install)
|
|
14130
14775
|
version, --version, -v Print the installed version
|
|
14131
14776
|
remove Remove everything (Chromium + MCP)
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "iframer",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.2",
|
|
5
5
|
"description": "Let iframer drive a Chrome tab you choose, on your real logged-in session.",
|
|
6
6
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp2wbce98k+xDnEgtuzLyNXYBLEDY0nqChuvolb94B3VV1HsHFUulUyNe92eN6MjQuadDohQ2j5mj7zWs4yQoixgTQPe7Gm/+GHWdgxwM2s9t5opnLHwF90LPiJm6q1NGWLM4aK6JT+2I6FnUGmj2bzB4WIlf55C3aMEBoXTqIzG2U8s2cxkXGebWWqbPlMK/Phsmsvo4POWnidbGHPhBOq0iyPTu7RdSDlNdJdJ+dtdNXkMmltKUS881MQI9T6GcCCKhIKcZrJGGnk0xphlWyl+ublTAO0tbQSVB/K7D3JmCxMv690TPtYUPPhGi+C6VCv3IeutLmNo60mvux3wbGQIDAQAB",
|
|
7
7
|
"permissions": [
|
package/package.json
CHANGED
package/skills/iframer.md
CHANGED
|
@@ -182,7 +182,10 @@ When you already drive a browser through your own tools (a T3 Code preview tab,
|
|
|
182
182
|
- Writes need `{approved:true}`: ask the user first. Replays stop for good on 403, 429 or a captcha page. Do not retry or work around it.
|
|
183
183
|
- Sites that detect tampering: `iframer kit --mode passive` (no patching; URLs and form posts only).
|
|
184
184
|
- Report coverage as the kit states it. It is never complete: workers, redirect hops and cross-origin iframes are invisible.
|
|
185
|
-
-
|
|
185
|
+
- Next.js sites: route segments come out named (`/{communitySlug}/...`) from the app's own route data. Page data: `window.__ifk.call('GET', path, {rsc:true})` (the generated client sets it) returns `{components: {Name: props}}`, not HTML. Server Actions: `window.__ifk.action('exportName', [args], {approved:true})`, or the client function of the same name. If a result is big you get its shape: pass `{full:true}` and pick fields in the same evaluate.
|
|
186
|
+
- After `iframer update`, the kit file is rewritten. Installing it in a tab that still runs an older kit replaces it and keeps the capture.
|
|
187
|
+
- Private values are separated from the API, not a reason to stop. `ingest` swaps anything token-, email- or phone-like for a placeholder (`{id}` in paths, `{key}` for keys) and lists what it replaced. Endpoints that had one carry `privateValues` in api.json: pass that value per call.
|
|
188
|
+
- Identity: api.json `identity` (and the knowledge file) says which auth headers the site sends and where the page keeps each value, e.g. `localStorage["session -> user.token"]`. `__ifk.call` attaches them on its own, cookies included. If a call returns 401 with a `hint`, use the site once so it sends its token, then retry.
|
|
186
189
|
|
|
187
190
|
### Handle captchas
|
|
188
191
|
|