har2sdk 0.6.3 → 0.6.4
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 +13 -0
- package/README.md +34 -0
- package/dist/codegen.d.ts +1 -1
- package/dist/codegen.js +1 -1
- package/dist/redact.js +16 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.4
|
|
4
|
+
|
|
5
|
+
A privacy fix in the mock's fake data, an Auth section in the README, and tests that pin both.
|
|
6
|
+
|
|
7
|
+
**Security**
|
|
8
|
+
- When a fake drawn for a recorded value happened to equal it (about 1 in 26³ for a three-letter name), the fallback reversed it: the mock served `"adA"` for `"Ada"`, `"7x"` for `"x7"`, the recorded value backwards and in the wrong shape. A collision now draws again from the same hash stream, so the fake never derives from the recorded value; a last-resort bump of one character remains for the case where 9 draws in a row all return it. Outputs without a collision are unchanged. Found by a CI failure in `review.test.ts`; `test/scrubber.test.ts` has the seeds that reproduce it and a generated run over 20,000 seeds (#4).
|
|
9
|
+
|
|
10
|
+
**Docs**
|
|
11
|
+
- README: an Auth section. The recorded token never reaches `client.ts`; which auth options a client gets; the token is sent with the scheme the app used (bare if it was sent bare, `Bearer` if no auth was seen); a token function is called before every request; a 401 throws `ApiError` without retry or refresh, with a refresh recipe; the mock doesn't check auth (#3).
|
|
12
|
+
|
|
13
|
+
**Tests**
|
|
14
|
+
- `test/auth.test.ts` pins every claim in the Auth section and runs its refresh recipe as written. `test/nullability.test.ts` records one field as a string, as `null` and left out, in every order, and checks `client.ts`, `openapi.json`, typechecking under `exactOptionalPropertyTypes` and the mock (#3).
|
|
15
|
+
|
|
3
16
|
## 0.6.3
|
|
4
17
|
|
|
5
18
|
Fixes from an eighth review, the last audit before publishing. Every finding has a test in `test/review8.test.ts` (written first and seen failing), and the second suite of the generated privacy test puts the person's name in a GraphQL comment and in a free-text `operationName`.
|
package/README.md
CHANGED
|
@@ -76,6 +76,40 @@ Needs Node 20+. Reads standard HAR 1.2: tested with Chrome-style exports and Pla
|
|
|
76
76
|
|
|
77
77
|
The generated `client.ts` has **zero dependencies**, uses the global `fetch` (Node 20+ and browsers), and compiles under `strict`, `exactOptionalPropertyTypes` and `erasableSyntaxOnly`. Non-2xx responses throw `ApiError` with the parsed body.
|
|
78
78
|
|
|
79
|
+
## Auth
|
|
80
|
+
|
|
81
|
+
The capture tells har2sdk *how* the app authenticated, never *with what*. The token, cookie or key you recorded is not copied into `client.ts` (if it were, the [leak guard](#privacy) would refuse to write the file). You pass your own:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
new AcmeShopClient({ token: process.env.TOKEN }); // a string
|
|
85
|
+
new AcmeShopClient({ token: () => getToken() }); // or a function, sync or async
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **Which options you get** depends on what the capture used: `token` (sent in `Authorization` with the scheme the app used, e.g. `Bearer …` or `Token …`, or bare if the app sent it bare; `Bearer` if no auth was seen), `basicAuth`, `apiKey` (in the header or query parameter the app used) and `cookie`. If no auth was seen, you get `token`.
|
|
89
|
+
- **A token function is called before every request**, so it can hand out a fresh token each time. For anything else computed per request (a CSRF header, a signature), pass `headers: async () => ({ … })`.
|
|
90
|
+
- **Refreshing is up to you.** On a `401` the client doesn't retry or refresh: it throws `ApiError` with `status`, the parsed `body` and the `operation` that failed. One way to handle it:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { AcmeShopClient, ApiError } from "./har2sdk-out/client.js";
|
|
94
|
+
|
|
95
|
+
let token = await login();
|
|
96
|
+
const shop = new AcmeShopClient({ token: () => token });
|
|
97
|
+
|
|
98
|
+
async function withRefresh<T>(call: () => Promise<T>): Promise<T> {
|
|
99
|
+
try {
|
|
100
|
+
return await call();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (!(err instanceof ApiError) || err.status !== 401) throw err;
|
|
103
|
+
token = await refresh();
|
|
104
|
+
return call();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const me = await withRefresh(() => shop.getMe());
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- **The mock doesn't check auth.** Any token, or none, gets the same response.
|
|
112
|
+
|
|
79
113
|
## Privacy
|
|
80
114
|
|
|
81
115
|
HAR files contain live session cookies, tokens and often other people's data. har2sdk:
|
package/dist/codegen.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* runtime dependencies (uses the global `fetch`, Node 18+ or any browser).
|
|
4
4
|
*/
|
|
5
5
|
import type { Plan, PlannedOp } from "./plan.js";
|
|
6
|
-
export declare const VERSION = "0.6.
|
|
6
|
+
export declare const VERSION = "0.6.4";
|
|
7
7
|
export declare function generateTypeScript(plan: Plan): string;
|
|
8
8
|
/** Just the type declarations (used for prompts and MCP responses). */
|
|
9
9
|
export declare function generateTypesOnly(plan: Plan, onlyOps?: PlannedOp[]): string;
|
package/dist/codegen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { printTs, printTsInterface } from "./types.js";
|
|
2
2
|
import { propertyKey } from "./words.js";
|
|
3
|
-
export const VERSION = "0.6.
|
|
3
|
+
export const VERSION = "0.6.4";
|
|
4
4
|
export function generateTypeScript(plan) {
|
|
5
5
|
const { model, clientName, ops } = plan;
|
|
6
6
|
const out = [];
|
package/dist/redact.js
CHANGED
|
@@ -576,7 +576,9 @@ export class Scrubber {
|
|
|
576
576
|
h ^= h << 5;
|
|
577
577
|
return h >>> 0;
|
|
578
578
|
};
|
|
579
|
-
const
|
|
579
|
+
const rest = v.slice(prefix.length);
|
|
580
|
+
const draw = () => [...rest]
|
|
581
|
+
.map((ch, i) => {
|
|
580
582
|
if (/[0-9]/.test(ch))
|
|
581
583
|
return String((next() % 9) + (i === 0 ? 1 : 0));
|
|
582
584
|
if (/[a-f]/.test(ch) && /^[0-9a-f-]+$/i.test(v))
|
|
@@ -586,13 +588,19 @@ export class Scrubber {
|
|
|
586
588
|
if (/[A-Z]/.test(ch))
|
|
587
589
|
return String.fromCharCode(65 + (next() % 26));
|
|
588
590
|
return ch;
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
591
|
+
})
|
|
592
|
+
.join("");
|
|
593
|
+
// Drew the recorded value itself: draw again from the same stream. Never derive the
|
|
594
|
+
// fake from the recorded value (reversing it kept it readable and broke its shape).
|
|
595
|
+
let body = draw();
|
|
596
|
+
for (let tries = 0; body === rest && tries < 8; tries++)
|
|
597
|
+
body = draw();
|
|
598
|
+
// Still the recorded value (only separators, or a very unlucky run): bump the first digit or letter.
|
|
599
|
+
// This bump is still derived from the recorded value, but it only runs after 9 draws in a row
|
|
600
|
+
// all returned it, which is next to impossible for any value with a digit or letter in it.
|
|
601
|
+
if (body === rest)
|
|
602
|
+
body = body.replace(/[0-9a-zA-Z]/, (c) => (/\d/.test(c) ? String((Number(c) + 1) % 10) : c === "z" ? "a" : c === "Z" ? "A" : String.fromCharCode(c.charCodeAt(0) + 1)));
|
|
603
|
+
f = prefix + body;
|
|
596
604
|
this.fakes.set(v, f);
|
|
597
605
|
return f;
|
|
598
606
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "har2sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Turn browser network traffic (HAR) into a typed TypeScript SDK, an OpenAPI 3.1 spec and a mock server. Local, deterministic, no API key. Ships an MCP server so Claude can name and document every endpoint.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"har",
|