@vitrinka/web 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @vitrinka/web
2
2
 
3
+ ## 0.1.1
4
+
5
+ - **The recorder never changes the page under test**: rrweb records images by
6
+ URL (`inlineImages: false`). Inlining made rrweb set
7
+ `crossOrigin="anonymous"` on the page's own no-CORS cross-origin `<img>`,
8
+ which refetched it in CORS mode and broke it on hosts without ACAO.
9
+ - **Link into the right workspace**: a `url` addressing `/w/<slug>`
10
+ preselects that workspace on the approve page, and a token approved into
11
+ any other one is discarded; the link sheet says `linked into <x> — this app
12
+ records into <slug>; link again and pick <slug>`. Needs `@vitrinka/link`
13
+ ≥ 0.1.1.
14
+
3
15
  ## 0.1.0
4
16
 
5
17
  - Initial release: the React DOM journey recorder (`@vitrinka/web/recorder`)
package/README.md CHANGED
@@ -56,8 +56,10 @@ testers **link their device from the pill**: the pill shows **Link
56
56
  recorder**, the sheet shows a short code, **Open vitrinka** (approve on this
57
57
  device) and a QR (scan from your phone); once approved the server mints an
58
58
  ingest-only `vkr_` token, stored in `localStorage` under
59
- `vitrinka.recorder.link`, and recording starts. **Unlink** in the ⋯ menu
60
- forgets it so does a 401 from the server. `recorderKey` (an admin-minted
59
+ `vitrinka.recorder.link`, and recording starts. A `url` addressing
60
+ `/w/<slug>` preselects that workspace on the approve page; a token approved
61
+ into another workspace is discarded and the sheet says which one to pick.
62
+ **Unlink** in the ⋯ menu forgets it — so does a 401 from the server. `recorderKey` (an admin-minted
61
63
  `vkr_` key) is for CI, e2e and unattended builds only; when passed it wins
62
64
  over the link. The prop is `recorderKey`, not `key`: React reserves `key`
63
65
  and never delivers it to a component.
@@ -146,7 +148,7 @@ __vitrinkaRecorder.status(); // { recording, sessionId, elapsedMs, queued, synce
146
148
 
147
149
  | Lane | Event `kind` | Payload |
148
150
  |---|---|---|
149
- | rrweb | `rrweb` | `{count}` + `blobKey` — the batch itself is uploaded as a chunk; inputs always masked, all text under a `maskAllText` policy |
151
+ | rrweb | `rrweb` | `{count}` + `blobKey` — the batch itself is uploaded as a chunk; inputs always masked, all text under a `maskAllText` policy; images recorded by URL, never inlined (inlining would alter the page's own `<img>`) |
150
152
  | clicks | `click` | `{selector, text, rect, route}` |
151
153
  | navigation | `nav` | `{url, route, spa}` |
152
154
  | network | `net` | `{method, url, status, ms, reqHeaders, resHeaders, reqBody, resBody, via}` — redacted, capped at 64 KiB per body; the recorder's own uploads are never recorded |
@@ -30,7 +30,11 @@ export function startRRWeb() {
30
30
  buf.push(ev);
31
31
  },
32
32
  checkoutEveryNms: CHECKOUT_MS,
33
- inlineImages: true,
33
+ // Never inline: to read a no-CORS cross-origin image rrweb sets
34
+ // crossOrigin='anonymous' on the page's own <img>, the browser refetches
35
+ // it in CORS mode and a host without ACAO shows a broken image — the
36
+ // recorder must never change the page under test. Replay loads by URL.
37
+ inlineImages: false,
34
38
  collectFonts: true,
35
39
  maskAllInputs: mask.maskAllInputs,
36
40
  ...(mask.maskTextSelector ? { maskTextSelector: mask.maskTextSelector } : {}),
@@ -6,11 +6,15 @@ import { type Linked, type LinkStart } from '@vitrinka/link';
6
6
  export { LinkExpired } from '@vitrinka/link';
7
7
  export interface DeviceLink {
8
8
  start: LinkStart;
9
- /** Resolves once the tester approved; rejects with LinkExpired / AbortError. */
9
+ /** Resolves once the tester approved; rejects with LinkExpired / LinkWorkspaceMismatch / AbortError. */
10
10
  linked: Promise<Linked>;
11
11
  cancel: () => void;
12
12
  }
13
- /** Ask for a code and poll until approved. The token is stored on success. */
13
+ /**
14
+ * Ask for a code and poll until approved. The token is stored on success.
15
+ * A `url` addressing `/w/<slug>` preselects that workspace on the approve
16
+ * page, and a token approved into any other one is discarded unstored.
17
+ */
14
18
  export declare function linkDevice(): Promise<DeviceLink>;
15
19
  /** Forget the stored link; a live session ends locally (its tail is dropped). */
16
20
  export declare function forgetLink(): void;
@@ -2,18 +2,23 @@
2
2
  * The device link, wired to the recorder: start a code, poll for approval,
3
3
  * store the token; forget it on Unlink or when a session door answers 401.
4
4
  */
5
- import { pollLink, startLink } from '@vitrinka/link';
5
+ import { linkWorkspace, pollLink, startLink } from '@vitrinka/link';
6
6
  import { onUnauthorized } from './api';
7
7
  import { clearLink, defaultLinkLabel, recorderConfig, storeLink } from './config';
8
8
  import { getState, resetQueues, setState } from './queue';
9
9
  import { notify } from './state';
10
10
  export { LinkExpired } from '@vitrinka/link';
11
- /** Ask for a code and poll until approved. The token is stored on success. */
11
+ /**
12
+ * Ask for a code and poll until approved. The token is stored on success.
13
+ * A `url` addressing `/w/<slug>` preselects that workspace on the approve
14
+ * page, and a token approved into any other one is discarded unstored.
15
+ */
12
16
  export async function linkDevice() {
13
17
  const { url } = recorderConfig();
14
- const start = await startLink(url, { label: defaultLinkLabel() });
18
+ const workspace = linkWorkspace(url);
19
+ const start = await startLink(url, { label: defaultLinkLabel(), workspace });
15
20
  const ac = new AbortController();
16
- const linked = pollLink(url, start.device_code, { interval: start.interval, signal: ac.signal }).then((l) => {
21
+ const linked = pollLink(url, start.device_code, { interval: start.interval, workspace, signal: ac.signal }).then((l) => {
17
22
  storeLink(l);
18
23
  return l;
19
24
  });
@@ -17,8 +17,8 @@ import { type SessionState } from './queue';
17
17
  export { currentRoute, notify, subscribe } from './state';
18
18
  export type { SessionDone } from '../protocol';
19
19
  /** Sent as `meta.recorder`; bumped with the package version. */
20
- export declare const RECORDER_VERSION = "0.1.0";
21
- export declare const RECORDER_ID = "web/0.1.0";
20
+ export declare const RECORDER_VERSION = "0.1.1";
21
+ export declare const RECORDER_ID = "web/0.1.1";
22
22
  /** What `POST /api/v1/sessions` answers (the fields this recorder keeps). */
23
23
  export interface SessionOut {
24
24
  id: string;
@@ -5,7 +5,7 @@ import { armReconcile, capturesSettled, disarmReconcile, drainBuffer, getState,
5
5
  import { currentRoute, notify } from './state';
6
6
  export { currentRoute, notify, subscribe } from './state';
7
7
  /** Sent as `meta.recorder`; bumped with the package version. */
8
- export const RECORDER_VERSION = '0.1.0';
8
+ export const RECORDER_VERSION = '0.1.1';
9
9
  export const RECORDER_ID = `web/${RECORDER_VERSION}`;
10
10
  /**
11
11
  * Re-apply a RECOVERED session's redaction policy after a reload — and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitrinka/web",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "vitrinka toolkit for React DOM apps — the journey recorder: capture manual-testing sessions (rrweb DOM stream, clicks, navigation, network, console, notes) straight into vitrinka boards.",
5
5
  "license": "Elastic-2.0",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "prepublishOnly": "tsc -p tsconfig.build.json"
46
46
  },
47
47
  "dependencies": {
48
- "@vitrinka/link": "^0.1.0",
48
+ "@vitrinka/link": "^0.1.1",
49
49
  "@vitrinka/redact": "^0.1.0"
50
50
  },
51
51
  "peerDependencies": {