@journeeze/sdk 0.0.0-stage → 0.1.0-rc.3

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.
Files changed (90) hide show
  1. package/AGENTS.md +80 -0
  2. package/CHANGELOG.md +41 -0
  3. package/LICENSE.md +27 -0
  4. package/README.md +343 -2
  5. package/dist/THIRD_PARTY_LICENSES.txt +246 -0
  6. package/dist/anchor-cPvFFTOS.js +1 -0
  7. package/dist/attach-DpioALxW.js +21 -0
  8. package/dist/chat-BgmB0rnW.js +64 -0
  9. package/dist/consent-DckCUvYR.js +1 -0
  10. package/dist/context-UnG6_X61.js +17 -0
  11. package/dist/contract.js +10 -0
  12. package/dist/feedback-CtH1a_kQ.js +1 -0
  13. package/dist/help-w0O5Qu-4.js +105 -0
  14. package/dist/hook-DGWypoEO.js +1 -0
  15. package/dist/index-B_nob8S0.js +1 -0
  16. package/dist/index-CwG2LrhO.js +20 -0
  17. package/dist/index.js +10 -0
  18. package/dist/jobs-CAXbM5-g.js +45 -0
  19. package/dist/loader-BZiIDD1D.js +96 -0
  20. package/dist/mask-audit-Du3NPoG_.js +5 -0
  21. package/dist/react.js +71 -0
  22. package/dist/resolve-BfWByuTp.js +1 -0
  23. package/dist/script.js +14 -0
  24. package/dist/signals-amjMQyOy.js +1 -0
  25. package/dist/telemetry-CyRJDaVs.js +1 -0
  26. package/dist/transport-B64CfIcF.js +1 -0
  27. package/dist/types/chat/capture/attach.d.ts +41 -0
  28. package/dist/types/chat/capture/draw.d.ts +10 -0
  29. package/dist/types/chat/capture/geometry.d.ts +30 -0
  30. package/dist/types/chat/capture/styles.d.ts +1 -0
  31. package/dist/types/chat/shell/chat.d.ts +27 -0
  32. package/dist/types/chat/shell/converse.d.ts +95 -0
  33. package/dist/types/chat/shell/copy.d.ts +8 -0
  34. package/dist/types/chat/shell/model.d.ts +36 -0
  35. package/dist/types/chat/shell/session.d.ts +148 -0
  36. package/dist/types/chat/shell/styles.d.ts +1 -0
  37. package/dist/types/chat/shell/view.d.ts +18 -0
  38. package/dist/types/chat/transport.d.ts +5 -0
  39. package/dist/types/config.d.ts +34 -0
  40. package/dist/types/context.d.ts +6 -0
  41. package/dist/types/contract.d.ts +13 -0
  42. package/dist/types/help/core/client.d.ts +26 -0
  43. package/dist/types/help/core/controller.d.ts +184 -0
  44. package/dist/types/help/core/events.d.ts +32 -0
  45. package/dist/types/help/core/help.d.ts +25 -0
  46. package/dist/types/help/core/highlight.d.ts +38 -0
  47. package/dist/types/help/core/panel.d.ts +27 -0
  48. package/dist/types/help/core/routing.d.ts +21 -0
  49. package/dist/types/help/core/search.d.ts +7 -0
  50. package/dist/types/help/ui/callout.d.ts +2 -0
  51. package/dist/types/help/ui/jobs/jobs.d.ts +24 -0
  52. package/dist/types/help/ui/jobs/model.d.ts +33 -0
  53. package/dist/types/help/ui/jobs/styles.d.ts +1 -0
  54. package/dist/types/help/ui/panel.d.ts +3 -0
  55. package/dist/types/help/ui/request.d.ts +2 -0
  56. package/dist/types/help/ui/styles.d.ts +1 -0
  57. package/dist/types/index.d.ts +7 -0
  58. package/dist/types/invite.d.ts +4 -0
  59. package/dist/types/loader.d.ts +59 -0
  60. package/dist/types/mask-detector.d.ts +22 -0
  61. package/dist/types/mask.d.ts +10 -0
  62. package/dist/types/preview/flag.d.ts +2 -0
  63. package/dist/types/preview/mask-audit.d.ts +29 -0
  64. package/dist/types/react.d.ts +9 -0
  65. package/dist/types/resolve/resolve.d.ts +12 -0
  66. package/dist/types/schemas/feedback.d.ts +76 -0
  67. package/dist/types/schemas/finding.d.ts +22 -0
  68. package/dist/types/schemas/index.d.ts +6 -0
  69. package/dist/types/schemas/turn.d.ts +26 -0
  70. package/dist/types/script.d.ts +6 -0
  71. package/dist/types/signals/cel.d.ts +8 -0
  72. package/dist/types/signals/hook.d.ts +10 -0
  73. package/dist/types/signals/signals.d.ts +149 -0
  74. package/dist/types/signals/steps.d.ts +33 -0
  75. package/dist/types/telemetry/telemetry.d.ts +73 -0
  76. package/dist/types/transport.d.ts +30 -0
  77. package/dist/types/types.d.ts +462 -0
  78. package/dist/types/widget/anchor.d.ts +11 -0
  79. package/dist/types/widget/capture.d.ts +39 -0
  80. package/dist/types/widget/consent.d.ts +25 -0
  81. package/dist/types/widget/feedback.d.ts +36 -0
  82. package/dist/types/widget/icons.d.ts +14 -0
  83. package/dist/types/widget/outbox.d.ts +69 -0
  84. package/dist/types/widget/styles.d.ts +1 -0
  85. package/dist/types/widget/upload.d.ts +14 -0
  86. package/dist/types/widget.d.ts +7 -0
  87. package/dist/types/wire.d.ts +66 -0
  88. package/dist/upload-Fu2qvSJV.js +14 -0
  89. package/dist/widget-B6mR1J3P.js +118 -0
  90. package/package.json +70 -4
package/AGENTS.md ADDED
@@ -0,0 +1,80 @@
1
+ # Integrating Journeeze — guide for coding agents
2
+
3
+ Rules for an agent adding `@journeeze/sdk` to a host app. The Journeeze contracts are the source
4
+ of truth: SDK (C0a), transport (C0b), journeys (C0c) and signals (C0d), with the integration guide.
5
+ The public types in this package (`dist/types`) follow them.
6
+
7
+ ## Install
8
+ - Public on npm: `npm install @journeeze/sdk@<exact version>`. No registry config or token.
9
+ - Pin an exact version. `-rc.N` = release candidates (`rc` dist-tag), plain = stable (`latest`).
10
+
11
+ ## Configuration
12
+ - `siteKey` (`pk_` + 32 hex, `SITE_KEY_PATTERN`) is **public** runtime config per site. It is not a secret.
13
+ - Direct mode is the default: the browser calls `https://app.journeeze.dev` with CORS for the
14
+ site's allowlisted origins. Set `apiHost` (script tag: `data-journeeze-api-host`) in every
15
+ environment except production: `https://app.staging.journeeze.dev` for a staging site key,
16
+ `http://localhost:8080` for the local stack, or the host's own origin when it runs a same-origin
17
+ proxy (then `apiPath` for a sub-path). Keep it in the same runtime config as the site key. The
18
+ SDK never infers it from where the script was loaded. `http:` is accepted only for
19
+ `localhost`/`127.0.0.1`.
20
+ - CSP, if the host sends one: `script-src 'self' https://app.journeeze.dev` (+ `'wasm-unsafe-eval'`
21
+ only with signals), `connect-src 'self' https://app.journeeze.dev`, `img-src 'self' blob:
22
+ https://app.journeeze.dev`, `media-src 'self' https://app.journeeze.dev`, and pass your `nonce`.
23
+ Use `app.staging.journeeze.dev` for staging.
24
+
25
+ ## Identity
26
+ - `userId` is optional. It must be `HMAC-SHA256(per-environment secret, internal user id)` computed
27
+ **on the server**, as 64 lowercase hex or 43 base64url characters (`USER_ID_PATTERN` from
28
+ `@journeeze/sdk/contract`). Never an email, name or raw id; never hashed in the browser.
29
+ - Deliver it only in an authenticated `Cache-Control: no-store` response for the signed-in user,
30
+ never in public runtime config.
31
+ - **Sign it (v1.3, sdk-v1.md §14).** In the same response, return `userIdExp` (Unix seconds, at
32
+ most 24 h ahead) and `userIdSig = base64url_nopad(HMAC-SHA256(site identity secret,
33
+ "jz-id-v1\n" + siteKey + "\n" + userId + "\n" + userIdExp))`, and pass both to the SDK
34
+ (`userIdSig`/`userIdExp` config or provider props, or `setUser(userId, { sig, exp })`). New sites
35
+ ignore unsigned ids: that feedback is anonymous and carries no screenshot. Check your
36
+ implementation against the published test vectors. The identity secret is a server secret, like
37
+ the HMAC secret above; re-issue the signature before it expires (the SDK never refreshes it).
38
+
39
+ ## React
40
+ - `<JourneezeProvider siteKey={…} userId={…} context={…}>` at the app root. While `siteKey` is
41
+ `undefined` the provider renders with an **inert handle** (no-ops, nothing sent); the first
42
+ defined key creates the real handle. `useJourneeze()` never throws inside the provider.
43
+ - Logout is the provider's lifecycle: `userId` changing away from an id, or the provider
44
+ unmounting, calls `reset()`. Do **not** call `reset()` yourself inside the provider. Script-tag
45
+ hosts call `window.Journeeze.reset()` on every logout path.
46
+ - `onHelpStatus(status)` (handle option or provider prop) reports `{ available: true }` or
47
+ `{ available: false, reason: "disabled" | "audience" }`; hide the host's own Help control on
48
+ `available: false`.
49
+
50
+ ## Context and privacy
51
+ - `context`: `persona`, `journeyId`, `journeyStep`, `device`, `businessType`, `state`.
52
+ `journeyId` is the journey's candidate id (C0c); `journeyStep` is a stable `stepRef` id such as
53
+ `route.projects.hub` — **never a URL, path or record id**. With route ids, set `pagePath: "none"`.
54
+ - Put `data-jz-mask` on every element showing customer content and `data-jz-block` on regions to
55
+ leave out of screenshots entirely. `maskSelectors` fail closed: one invalid selector disables
56
+ screenshots for that handle.
57
+ - The SDK asks for consent before any capture. Global Privacy Control is honoured
58
+ (`respectGlobalPrivacyControl`, default on); Do Not Track is ignored (deprecated).
59
+ - Give guided elements a stable `data-tflow-id`; guides fall back to test id, role + name, CSS.
60
+
61
+ ## Signals (optional)
62
+ - `signals: { enabled: true, tflo: () => import("tflo-browser-events") }`. If the host already uses
63
+ `tflo-browser-events` (`>=0.1.0 <1`, optional peer dependency), **inject it** so the page has
64
+ one tflo runtime; otherwise the SDK lazy-loads its own copy after consent.
65
+ - Signals start only after the same consent as feedback, and stop when the site's kill switch is off.
66
+
67
+ ## Script tag
68
+ - `window.Journeeze` exists only after the module script runs, which is after inline scripts. Use
69
+ it from a user event or after `DOMContentLoaded`, never during page load.
70
+
71
+ ## GitHub
72
+ - Issues and the `.jevitate/` catalog go through the Journeeze GitHub App
73
+ (`https://github.com/apps/journeeze/installations/new`), installed by the product owner on the
74
+ repository named at setup. Never use a personal access token.
75
+
76
+ ## Never
77
+ - Put a secret in the bundle (the HMAC secret, tokens, gateway keys).
78
+ - Send raw user ids, emails, names, URLs or record ids anywhere (context, steps, rules, params).
79
+ - Load a second tflo runtime next to the host's.
80
+ - Disable masking or skip the consent step.
package/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0-rc.3 (not yet published)
4
+
5
+ - **Setup documentation:** the README is a self-contained setup guide: what the Journeeze team sends you, install with a pinned version, `apiHost` per environment, CSP, signed identity with a test vector, privacy and masking, help, feedback, signals, the Journeeze GitHub App and its permissions, a checklist and troubleshooting. Examples use `window.Journeeze` only after `DOMContentLoaded`. The license line matches `LICENSE.md`.
6
+ - **Help chat on the live `Converse` (HC8c):** each turn carries the reviewer token (when the host has one), and on consented turns the signed user id (`userId`, `userIdExp`, `userIdSig`) and the detector's over-masking counts (`overMasked`, counts per rule only). Closing the chat (✕ or Esc) ends a started conversation with a `cancel` turn; opening it again starts a new one.
7
+ - **Chat copy as data (ADR 0020 §5):** the help chat ships no words of its own. Its chrome comes from the site's `widget.ui.*` copy (new `GetWidgetConfig.copy`, by full key, `{name}` placeholders filled by the widget), and its buttons, chips and "+" menu use the labels the server sends on each `Converse` action (`label`, `menu[].label/hint`). The chat reads `GetWidgetConfig` once (copy and mask selectors). If that call fails, only the launcher keeps a last-resort accessible name.
8
+ - **Help chat shell (SDK 2.x, HC8a), behind `chat: true`:** `enableHelp()` (or `data-journeeze-chat` on the script tag) shows one "?" launcher that opens a conversation instead of the v1 help panel: job chips filtered as you type ("Other — none of these" last), inline guide cards, a composer with a "+" menu, a one-time consent line before anything is stored, a removable "Sent with this report" row and, for reviewers, "Team details". Turns go to the collector's `Converse` (direct-mode contract v2 §2). New `feedback()` on the handle opens the chat at "Report a problem" (without `chat: true` it switches feedback mode on). The chat is its own lazy chunk (≤ 11.5 KB gz).
9
+ - **Intent routing in help (P6d):** when a reader who has already consented sends "Something else", help first asks the collector (`RouteIntent`) which of the site's jobs their words mean. A confident match with a guide shows "Did you mean: <job>?" with "Show me how" (its guide) and "No, send my request"; otherwise the request is sent as before. The answer is only a job id and its guide, never generated text; the words are not stored by routing. Without consent nothing is routed.
10
+ - **Jobs in help (P4e2):** when the site's help index lists jobs, help opens on "What are you trying to do?": the jobs common for the host's `persona` under "Common for you", the rest under "More", a local filter, and "Something else". A job opens its guide (the persona's own when it has one) and ends with "Got it done?" (yes / partly / no, plus "Harder than it should be"); a job without a guide offers "Not possible here". `job_picked` / `job_not_possible` / `job_outcome` are aggregate engagement events (job id and entry version only), sent only after consent, with help on and never under Global Privacy Control. "Something else" is the reader's own words as a consented feedback request (with the optional "so you can…"), never telemetry. The jobs view is a separate lazy chunk (≤ 5 KB gz), loaded only for sites with jobs.
11
+
12
+ ## 0.1.0-rc.2 (2026-10-09)
13
+
14
+ First version published to the public npm registry.
15
+
16
+ - **Public on npm:** published to the npm registry as `@journeeze/sdk` (no registry config or token), starting with 0.1.0-rc.2. GitHub Packages (`@matt-cochran-products/journeeze-sdk`) is no longer published. The package is licensed for use with the Journeeze service (`LICENSE.md`); it is not open source.
17
+ - **Signed host identity (contract C0a §4, v1.3):** `userIdSig` / `userIdExp` config fields and provider props, and `setUser(userId, { sig, exp })`. The signature is sent with feedback and end-user screenshot uploads while it is unexpired; the same id with a new signature only refreshes it. Sites that require signed identity treat an unsigned id as anonymous.
18
+ - The SDK's own lazy tflo copy (used only when the host injects none) is `tflo-browser-events@0.3.0`: no element text, no `document.title` and path-only URLs by default. Hosts that inject their own tflo are unaffected; the 0.1.0 capability floor is still tested.
19
+
20
+ ## 0.1.0-rc.1
21
+
22
+ First release candidate of the v1 SDK (epic #75). Published to GitHub Packages as
23
+ `@matt-cochran-products/journeeze-sdk`; install it as `@journeeze/sdk` (see `docs/integrating.md`).
24
+
25
+ - **Contracts:** implements SDK contract v1 (C0a v1.1-r3 + v1.2), direct-mode transport (C0b v1.1-r3 +
26
+ v1.2), journey import (C0c v1.1-r3) and signals (C0d v1.1-r3 + v1.2), all signed off by Allumata.
27
+ - **Direct mode by default:** the browser talks to Journeeze directly (CORS for allowlisted origins,
28
+ `text/plain`, no preflight); `apiHost`/`apiPath` keep a same-origin proxy working. One 32 KiB
29
+ page-end budget, keepalive `fetch` first, retries honouring `Retry-After`, idempotent
30
+ `submissionId`.
31
+ - **Handle, identity and context:** `setContext`, opaque host-computed `userId` (HMAC digest only,
32
+ sent on feedback), `reset()` on logout, per-owner outbox and consent, React provider lifecycle
33
+ with an inert handle until `siteKey` is known, `pagePath: "redacted" | "none"`.
34
+ - **Feedback:** masked screenshots with preview before send; fail-closed `maskSelectors`;
35
+ `data-jz-block`; signed-in end users can attach screenshots (anonymous visitors never do).
36
+ - **Help:** in-app guides with fallback target resolution (tflo id → test id → role + name → CSS)
37
+ in its own lazy chunk; public or reviewers-only audience; `onHelpStatus` reports availability.
38
+ - **Signals:** opt-in, consent-gated derived signals via `tflo-browser-events` (host-injected or the
39
+ SDK's own lazy 0.2.0 copy); platform rules `jz-platform@1` plus registered site rules; no
40
+ session id, no sampling; the site kill switch is honoured.
41
+ - **Size:** entry ≤ 2 KiB gzipped; everything else loads lazily.
package/LICENSE.md ADDED
@@ -0,0 +1,27 @@
1
+ # Journeeze SDK License
2
+
3
+ Copyright (c) 2026 Matt Cochran Products. All rights reserved.
4
+
5
+ This package (`@journeeze/sdk`) is proprietary software. It is not open source.
6
+
7
+ You may install this package and include it, unmodified, in websites and applications
8
+ (including bundled and minified builds) solely to connect them to the Journeeze service, under
9
+ the terms of your agreement for that service.
10
+
11
+ You may not:
12
+
13
+ - modify, adapt or create derivative works of this package, other than the bundling and
14
+ minification your build tools perform;
15
+ - distribute this package on its own, or as part of anything other than a website or
16
+ application that uses the Journeeze service;
17
+ - decompile, reverse engineer or otherwise attempt to derive source code from this package,
18
+ except to the extent applicable law expressly permits it;
19
+ - use this package to build or operate a product or service that competes with Journeeze.
20
+
21
+ Third-party components included in this package are licensed under their own terms; see
22
+ `dist/THIRD_PARTY_LICENSES.txt`.
23
+
24
+ THIS PACKAGE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
25
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR
27
+ OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THIS PACKAGE OR ITS USE.
package/README.md CHANGED
@@ -1,3 +1,344 @@
1
- # Temporary Holding Version
1
+ # @journeeze/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Journeeze adds in-app help and an opt-in feedback mode to any website or app. Visitors read
4
+ step-by-step guides built from your documented user journeys, and invited reviewers (or signed-in
5
+ users) point at what is unclear. Journeeze groups that feedback and files it as GitHub issues in
6
+ your repository.
7
+
8
+ The SDK is a small loader (about 2 KB gzipped). Nothing observes the page until help or the
9
+ feedback mode is switched on, and nothing is captured before the visitor consents.
10
+
11
+ **Contents:** [Before you start](#before-you-start) · [Install](#install) ·
12
+ [Environments](#environments-and-apihost) · [Content Security Policy](#content-security-policy) ·
13
+ [Signed-in users](#signed-in-users) · [Privacy and masking](#privacy-and-masking) ·
14
+ [Help](#in-app-help) · [Feedback mode](#feedback-mode) · [Signals](#signals-optional) ·
15
+ [GitHub](#connect-github) · [Checklist](#setup-checklist) ·
16
+ [Troubleshooting](#troubleshooting) · [Versions](#versions-and-license)
17
+
18
+ ## Before you start
19
+
20
+ Journeeze is in early access. The Journeeze team sets up your account. Send them:
21
+
22
+ 1. Your organisation and product name.
23
+ 2. Every origin the SDK will run on, with scheme, host and port. Example:
24
+ `https://app.example.com`, `https://*.preview.example.com`. Staging and production are separate.
25
+ 3. Whether visitors can be anonymous, or every user is signed in.
26
+ 4. The GitHub repository issues should go to (`owner/repo`).
27
+ 5. Who should be the project owner on the review board.
28
+
29
+ You get back:
30
+
31
+ | You receive | What it is | Where it goes |
32
+ |---|---|---|
33
+ | A **site key** per site (`pk_` + 32 hex) | Public identifier. Not a secret. | Your page or app runtime config |
34
+ | An **identity secret** per site (64 hex) | Server secret for [signing user ids](#signed-in-users). Sent as a one-time 1Password link, never by email or chat. | Your server's secret store only |
35
+ | An **owner invite link** | Opens the review board as project owner. Single use. | Open it once, in your browser |
36
+
37
+ ## Install
38
+
39
+ Pick one way. Both load the same code.
40
+
41
+ ### npm (bundled apps)
42
+
43
+ ```sh
44
+ npm install @journeeze/sdk@0.1.0-rc.3 --save-exact
45
+ ```
46
+
47
+ Pin an exact version. Release candidates (`-rc.N`) are on the `rc` tag; stable releases on
48
+ `latest`.
49
+
50
+ **React:**
51
+
52
+ ```tsx
53
+ import { JourneezeProvider, useJourneeze } from "@journeeze/sdk/react";
54
+
55
+ export function Root() {
56
+ return (
57
+ <JourneezeProvider
58
+ siteKey={config.journeezeSiteKey} // public, from runtime config
59
+ apiHost={config.journeezeApiHost} // staging and local only; see "Environments"
60
+ context={{ persona: "owner", journeyId: "billing", journeyStep: "route.billing.pay" }}
61
+ pagePath="none" // you pass route ids as journeyStep
62
+ >
63
+ <App />
64
+ </JourneezeProvider>
65
+ );
66
+ }
67
+
68
+ function HelpButton() {
69
+ const jz = useJourneeze();
70
+ return <button onClick={() => jz.enableHelp()}>Help</button>;
71
+ }
72
+ ```
73
+
74
+ - While `siteKey` is `undefined` (config still loading) the provider renders with an inert handle:
75
+ every method does nothing and nothing is sent. The real handle starts with the first key.
76
+ - Keep `context` in sync with your router. The provider applies every change.
77
+ - With TypeScript's `exactOptionalPropertyTypes`, optional props do not accept `undefined`. Pass
78
+ them only when set: `{...(config.journeezeApiHost ? { apiHost: config.journeezeApiHost } : {})}`.
79
+
80
+ **Without React:**
81
+
82
+ ```ts
83
+ import { createJourneeze } from "@journeeze/sdk";
84
+
85
+ const jz = createJourneeze({ siteKey: "pk_...", apiHost: "https://app.staging.journeeze.dev" });
86
+ document.querySelector("#help")?.addEventListener("click", () => jz.enableHelp());
87
+ ```
88
+
89
+ ### Script tag (no bundler)
90
+
91
+ ```html
92
+ <script type="module"
93
+ src="https://app.journeeze.dev/sdk/script.js"
94
+ data-journeeze-site-key="pk_..."
95
+ data-journeeze-help></script>
96
+ ```
97
+
98
+ The tag creates `window.Journeeze`. It is a module script, so it runs after your inline scripts.
99
+ Use it from a click, or after `DOMContentLoaded`, never while the page is still loading:
100
+
101
+ ```html
102
+ <button id="suggest">Suggest an improvement</button>
103
+ <script>
104
+ document.addEventListener("DOMContentLoaded", () => {
105
+ document.getElementById("suggest").addEventListener("click", () => window.Journeeze?.enable());
106
+ });
107
+ </script>
108
+ ```
109
+
110
+ | Attribute | Effect |
111
+ |---|---|
112
+ | `data-journeeze-site-key` | Required. Your site key. |
113
+ | `data-journeeze-api-host` | The collector origin. Required outside production; see below. |
114
+ | `data-journeeze-api-path` | Path prefix, only for a same-origin proxy. |
115
+ | `data-journeeze-help` | Shows the help launcher at once. |
116
+ | `data-journeeze-chat` | Help opens as a conversation instead of the guide panel. |
117
+
118
+ `/sdk/script.js` is always the SDK version the Journeeze service currently runs. To control the
119
+ version yourself, install from npm and pin it.
120
+
121
+ ## Environments and `apiHost`
122
+
123
+ The SDK sends to production (`https://app.journeeze.dev`) unless you set `apiHost`. It does not
124
+ follow where the script was loaded from. A staging or local site key sent to production is
125
+ refused, so set `apiHost` next to the site key in each environment's config:
126
+
127
+ | Environment | `apiHost` / `data-journeeze-api-host` |
128
+ |---|---|
129
+ | Production | leave it out |
130
+ | Staging | `https://app.staging.journeeze.dev` |
131
+ | Local Journeeze stack | `http://localhost:8080` |
132
+ | Your own same-origin proxy | your origin, for example `https://www.example.com` |
133
+
134
+ The browser calls the collector directly with CORS, only from the origins you registered. Requests
135
+ carry no cookies and are not cached. A proxy is optional. It suits offices or CI where many users
136
+ share one IP address, or a policy that allows no third-party requests. The Journeeze team sends the
137
+ proxy guide (gateway key, the paths to forward, reference Worker and server code) on request.
138
+
139
+ ## Content Security Policy
140
+
141
+ If your site sends a CSP, add these. Use `https://app.staging.journeeze.dev` on staging.
142
+
143
+ ```
144
+ script-src 'self' https://app.journeeze.dev;
145
+ connect-src 'self' https://app.journeeze.dev;
146
+ img-src 'self' blob: https://app.journeeze.dev;
147
+ media-src 'self' https://app.journeeze.dev;
148
+ style-src 'self' 'nonce-<your nonce>';
149
+ ```
150
+
151
+ - Add `'wasm-unsafe-eval'` to `script-src` only if you turn on [signals](#signals-optional).
152
+ - Pass your nonce as the `nonce` option. The SDK puts it on the styles it injects.
153
+ - With npm and a proxy, `script-src` and `connect-src` need only `'self'`.
154
+
155
+ ## Signed-in users
156
+
157
+ Attributing feedback to a user is optional. When you do, your server makes the user id and signs
158
+ it. The browser never sees either secret.
159
+
160
+ 1. **User id.** `HMAC-SHA256(your own secret, your internal user id)`, as 64 lowercase hex or
161
+ 43 base64url characters. Never an email, name or raw id. The SDK ignores anything else with a
162
+ console warning. Keep this secret stable: changing it splits every user's history.
163
+ 2. **Signature.** With the identity secret you received for the site:
164
+
165
+ ```ts
166
+ import { createHmac } from "node:crypto";
167
+
168
+ export function signUserId(identitySecretHex: string, siteKey: string, userId: string) {
169
+ const userIdExp = Math.floor(Date.now() / 1000) + 3600; // at most 24 h ahead
170
+ const userIdSig = createHmac("sha256", Buffer.from(identitySecretHex, "hex"))
171
+ .update(`jz-id-v1\n${siteKey}\n${userId}\n${userIdExp}`)
172
+ .digest("base64url");
173
+ return { userId, userIdExp, userIdSig };
174
+ }
175
+ ```
176
+
177
+ Check your code against this vector. Secret: the bytes `0x00` to `0x1f` (hex
178
+ `000102…1e1f`). Site key `pk_0123456789abcdef0123456789abcdef`. User id: `a` × 64.
179
+ `userIdExp` `1800000000`. Expected `userIdSig`: `IluU_qUHY6OdSzqKu0Y_wS0sDB53qVOOnobfdHuCnjI`.
180
+ 3. **Deliver** `{ userId, userIdExp, userIdSig }` from an authenticated endpoint with
181
+ `Cache-Control: no-store`. Never put them in public config.
182
+ 4. **Pass them to the SDK:** provider props `userId`, `userIdSig`, `userIdExp`, or
183
+ `jz.setUser(userId, { sig, exp })`. Re-issue the signature before it expires. The SDK never
184
+ refreshes it.
185
+ 5. **Logout.** The React provider calls `reset()` when `userId` goes away or the provider
186
+ unmounts. Without React, call `jz.reset()` (or `window.Journeeze.reset()`) on every logout path.
187
+ `reset()` sends that user's unsent feedback once, then clears it, the context, consent and the
188
+ reviewer token from the device.
189
+
190
+ Sites require the signature by default. With a missing, expired or wrong signature, that feedback
191
+ is anonymous and has no screenshot.
192
+
193
+ ## Privacy and masking
194
+
195
+ - Nothing is captured until the visitor turns the feedback mode on and consents. Reading help
196
+ needs no consent.
197
+ - Screenshots are masked in the browser before encoding, and the visitor sees the masked image
198
+ before sending. They can always send without it.
199
+ - Always masked: inputs, textareas, selects, editable regions, video, audio, canvas, frames and
200
+ `[data-jz-mask]`. The SDK also masks elements that look sensitive (password and card fields, API
201
+ keys, IBANs, email-shaped text). Mark such elements yourself rather than relying on detection.
202
+ - `[data-jz-block]` leaves an empty box that cannot be picked.
203
+ - `maskSelectors: [".customer-name"]` and `maskAllText: true` widen masking. One invalid selector
204
+ turns screenshots off (fails closed).
205
+ - Anonymous visitors never capture or send a screenshot.
206
+ - `pagePath`: `"redacted"` (default) replaces id-like path segments with `:id`; `"none"` sends no
207
+ path.
208
+ - Global Privacy Control turns help telemetry and signals off (`respectGlobalPrivacyControl`,
209
+ default `true`).
210
+ - The collector accepts a closed list of fields. Raw ids, emails, URLs and page text are not on it.
211
+
212
+ Put `data-jz-mask` on every element that shows customer content.
213
+
214
+ ## In-app help
215
+
216
+ Help shows a launcher that opens guides built from your published journeys: search, step-by-step
217
+ guidance that outlines each step's element, and an optional "Did this step make sense?" check-in.
218
+
219
+ ```ts
220
+ await jz.enableHelp(); // loads the help code and shows the launcher
221
+ jz.isHelpEnabled(); // true
222
+ jz.disableHelp(); // removes it
223
+ ```
224
+
225
+ - `onHelpStatus(status)` tells you whether help is available here: `{ available: true }`, or
226
+ `{ available: false, reason: "disabled" }` (help is off for the site) or `"audience"` (help is
227
+ for reviewers only and this visitor is not one). Hide your own Help button on `false`.
228
+ - Steps point at elements by `data-tflow-id="area.element"` (lowercase letters, digits and `._:-`,
229
+ up to 96 characters). Give every element a guide points at a stable `data-tflow-id`. A step whose
230
+ element is not on the page shows its text without an outline.
231
+ - Set `journeyStep` in `context` to a stable id for where the user is (a route id such as
232
+ `route.projects.hub`), never a URL or record id. Guides and signals match on it.
233
+ - Help is switched on per site on the review board: **Settings → the site → In-app help**. Choose
234
+ **Reviewers** (default) or **Public visitors**.
235
+
236
+ Where guides come from: see [Connect GitHub](#connect-github).
237
+
238
+ ## Feedback mode
239
+
240
+ ```ts
241
+ await jz.enable(); // turns the feedback mode on (asks for consent first)
242
+ jz.disable();
243
+ jz.isEnabled();
244
+ ```
245
+
246
+ - Invited reviewers arrive with `?jz_invite=<token>`. The SDK reads it once, removes it from the
247
+ URL and keeps it for the tab.
248
+ - `onModeChange(enabled)` reports every switch, including Esc and Exit inside the widget.
249
+ - When `journeyStep` changes, the widget can ask about the step the reviewer just finished.
250
+
251
+ ## Signals (optional)
252
+
253
+ Aggregate behaviour signals: rage clicks, dead clicks, hesitation, abandoned journey steps and your
254
+ own registered signals. They are computed in the browser. Only the derived signal is sent, with no
255
+ URL, text, user id or session id.
256
+
257
+ ```ts
258
+ createJourneeze({
259
+ siteKey,
260
+ signals: {
261
+ enabled: true,
262
+ tflo: () => import("tflo-browser-events"), // pass your copy if your app already uses it
263
+ },
264
+ });
265
+ ```
266
+
267
+ - They run only after consent, while help or feedback is on, and while the site's signals switch on
268
+ the board is on. Turning that switch off stops them without a deploy.
269
+ - Only elements with a `data-tflow-id` outside masked regions are tracked. The SDK listens to
270
+ clicks, hover, focus and change events. It never reads keystrokes, input values or pointer
271
+ movement.
272
+ - Without your own `tflo-browser-events` (`>=0.1.0 <1`, optional peer dependency) the SDK loads its
273
+ own copy. That needs `'wasm-unsafe-eval'` in `script-src` and `data:` in `connect-src`.
274
+
275
+ ## Connect GitHub
276
+
277
+ Journeeze files issues and reads your journey catalog through the **Journeeze GitHub App**.
278
+ Install it on the repository you named during setup:
279
+
280
+ **<https://github.com/apps/journeeze/installations/new>** → choose your account → **Only select
281
+ repositories** → your repository → **Install**.
282
+
283
+ | Permission | Why Journeeze needs it |
284
+ |---|---|
285
+ | Issues: read and write | File a cluster of feedback as one issue, and follow it until it is closed. |
286
+ | Contents: read and write | Read your `.jevitate/` journey catalog. Writes go only to a Journeeze branch, as a pull request you review. |
287
+ | Pull requests: read and write | Open those catalog pull requests and read their status. |
288
+ | Metadata: read | Basic repository information GitHub requires for every App. |
289
+
290
+ Each call uses a one-hour token limited to that repository. You can remove the App at any time
291
+ (GitHub → Settings → Applications → Installed GitHub Apps). Journeeze then stops reading and
292
+ filing.
293
+
294
+ **After installing, tell the Journeeze team.** They link the repository to your project. This is
295
+ one step on their side until the board does it for you.
296
+
297
+ **Your journey catalog.** Journeeze reads the Jevitate catalog in your repository's `.jevitate/`
298
+ folder (personas, jobs and journeys) on each push. Guides with screenshots and video are imported
299
+ from your approved journeys as drafts. A triager reviews each one on the board (**Journeys**), may
300
+ lightly edit the step text, and publishes it. Only published guides appear in help.
301
+
302
+ **Without the App: upload from CI.** Your CI can upload the catalog as a bundle instead:
303
+ `POST https://app.journeeze.dev/api/upload/v1/bundles` with `Authorization: Bearer jzu_…`. The
304
+ Journeeze team creates upload keys for now and sends them as a one-time 1Password link. Ask them
305
+ for the bundle format and a CI example. Never put an upload key in a URL or a log.
306
+
307
+ ## Setup checklist
308
+
309
+ - [ ] Site key (and `apiHost` outside production) in each environment's runtime config.
310
+ - [ ] SDK installed with a pinned version, or the script tag on every page.
311
+ - [ ] CSP updated, if you send one.
312
+ - [ ] `data-jz-mask` on every element with customer content; masked screenshot checked on each
313
+ screen that shows data.
314
+ - [ ] `data-tflow-id` on every element a guide points at; `journeyStep` set per route.
315
+ - [ ] Signed-in users: user id and signature made on your server and delivered `no-store`;
316
+ `reset()` on every logout path.
317
+ - [ ] Journeeze GitHub App installed on your repository, and the Journeeze team told.
318
+ - [ ] Help switched on for the site on the board, audience chosen, guides published.
319
+ - [ ] One test feedback item sent from each site and seen on the board.
320
+
321
+ ## Troubleshooting
322
+
323
+ | Symptom | Cause and fix |
324
+ |---|---|
325
+ | Requests go to `app.journeeze.dev` from staging or local, or the site key is refused | `apiHost` is not set. Set it for that environment. |
326
+ | CORS error | The page origin is not registered for the site (scheme, host and port must match exactly). Ask the Journeeze team to add it. |
327
+ | `window.Journeeze` is undefined | It was used during page load. Use it from a click or after `DOMContentLoaded`. |
328
+ | Help launcher never appears | Help is off for the site, or the audience is Reviewers and this visitor has no invite. Check `onHelpStatus`. |
329
+ | A guide step has no outline | The element's `data-tflow-id` is missing or changed. |
330
+ | No screenshot offered | An invalid `maskSelectors` entry (fails closed), or the visitor is anonymous. |
331
+ | Feedback is anonymous although `userId` is set | The signature is missing, wrong, expired, or made for another site key. |
332
+ | `userId` ignored with a console warning | It is not a 64-hex or 43-base64url hash (for example a raw UUID or an email). |
333
+ | `429` responses from an office or CI | Many users share one IP address. Use a proxy, or ask for a higher quota. |
334
+ | Issues are never filed | The Journeeze GitHub App is not installed on the repository, or the Journeeze team has not linked it yet. |
335
+
336
+ ## Versions and license
337
+
338
+ Changes are listed in `CHANGELOG.md` in this package. Public types ship in `dist/types`.
339
+ `@journeeze/sdk/contract` exports `SITE_KEY_PATTERN` and `USER_ID_PATTERN` for your server's
340
+ tests.
341
+
342
+ Proprietary: see `LICENSE.md`. You may install this package and include it, unmodified, in
343
+ websites and applications solely to connect them to the Journeeze service, under your agreement for
344
+ that service. It is not open source.