@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.
- package/AGENTS.md +80 -0
- package/CHANGELOG.md +41 -0
- package/LICENSE.md +27 -0
- package/README.md +343 -2
- package/dist/THIRD_PARTY_LICENSES.txt +246 -0
- package/dist/anchor-cPvFFTOS.js +1 -0
- package/dist/attach-DpioALxW.js +21 -0
- package/dist/chat-BgmB0rnW.js +64 -0
- package/dist/consent-DckCUvYR.js +1 -0
- package/dist/context-UnG6_X61.js +17 -0
- package/dist/contract.js +10 -0
- package/dist/feedback-CtH1a_kQ.js +1 -0
- package/dist/help-w0O5Qu-4.js +105 -0
- package/dist/hook-DGWypoEO.js +1 -0
- package/dist/index-B_nob8S0.js +1 -0
- package/dist/index-CwG2LrhO.js +20 -0
- package/dist/index.js +10 -0
- package/dist/jobs-CAXbM5-g.js +45 -0
- package/dist/loader-BZiIDD1D.js +96 -0
- package/dist/mask-audit-Du3NPoG_.js +5 -0
- package/dist/react.js +71 -0
- package/dist/resolve-BfWByuTp.js +1 -0
- package/dist/script.js +14 -0
- package/dist/signals-amjMQyOy.js +1 -0
- package/dist/telemetry-CyRJDaVs.js +1 -0
- package/dist/transport-B64CfIcF.js +1 -0
- package/dist/types/chat/capture/attach.d.ts +41 -0
- package/dist/types/chat/capture/draw.d.ts +10 -0
- package/dist/types/chat/capture/geometry.d.ts +30 -0
- package/dist/types/chat/capture/styles.d.ts +1 -0
- package/dist/types/chat/shell/chat.d.ts +27 -0
- package/dist/types/chat/shell/converse.d.ts +95 -0
- package/dist/types/chat/shell/copy.d.ts +8 -0
- package/dist/types/chat/shell/model.d.ts +36 -0
- package/dist/types/chat/shell/session.d.ts +148 -0
- package/dist/types/chat/shell/styles.d.ts +1 -0
- package/dist/types/chat/shell/view.d.ts +18 -0
- package/dist/types/chat/transport.d.ts +5 -0
- package/dist/types/config.d.ts +34 -0
- package/dist/types/context.d.ts +6 -0
- package/dist/types/contract.d.ts +13 -0
- package/dist/types/help/core/client.d.ts +26 -0
- package/dist/types/help/core/controller.d.ts +184 -0
- package/dist/types/help/core/events.d.ts +32 -0
- package/dist/types/help/core/help.d.ts +25 -0
- package/dist/types/help/core/highlight.d.ts +38 -0
- package/dist/types/help/core/panel.d.ts +27 -0
- package/dist/types/help/core/routing.d.ts +21 -0
- package/dist/types/help/core/search.d.ts +7 -0
- package/dist/types/help/ui/callout.d.ts +2 -0
- package/dist/types/help/ui/jobs/jobs.d.ts +24 -0
- package/dist/types/help/ui/jobs/model.d.ts +33 -0
- package/dist/types/help/ui/jobs/styles.d.ts +1 -0
- package/dist/types/help/ui/panel.d.ts +3 -0
- package/dist/types/help/ui/request.d.ts +2 -0
- package/dist/types/help/ui/styles.d.ts +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/invite.d.ts +4 -0
- package/dist/types/loader.d.ts +59 -0
- package/dist/types/mask-detector.d.ts +22 -0
- package/dist/types/mask.d.ts +10 -0
- package/dist/types/preview/flag.d.ts +2 -0
- package/dist/types/preview/mask-audit.d.ts +29 -0
- package/dist/types/react.d.ts +9 -0
- package/dist/types/resolve/resolve.d.ts +12 -0
- package/dist/types/schemas/feedback.d.ts +76 -0
- package/dist/types/schemas/finding.d.ts +22 -0
- package/dist/types/schemas/index.d.ts +6 -0
- package/dist/types/schemas/turn.d.ts +26 -0
- package/dist/types/script.d.ts +6 -0
- package/dist/types/signals/cel.d.ts +8 -0
- package/dist/types/signals/hook.d.ts +10 -0
- package/dist/types/signals/signals.d.ts +149 -0
- package/dist/types/signals/steps.d.ts +33 -0
- package/dist/types/telemetry/telemetry.d.ts +73 -0
- package/dist/types/transport.d.ts +30 -0
- package/dist/types/types.d.ts +462 -0
- package/dist/types/widget/anchor.d.ts +11 -0
- package/dist/types/widget/capture.d.ts +39 -0
- package/dist/types/widget/consent.d.ts +25 -0
- package/dist/types/widget/feedback.d.ts +36 -0
- package/dist/types/widget/icons.d.ts +14 -0
- package/dist/types/widget/outbox.d.ts +69 -0
- package/dist/types/widget/styles.d.ts +1 -0
- package/dist/types/widget/upload.d.ts +14 -0
- package/dist/types/widget.d.ts +7 -0
- package/dist/types/wire.d.ts +66 -0
- package/dist/upload-Fu2qvSJV.js +14 -0
- package/dist/widget-B6mR1J3P.js +118 -0
- 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
|
-
#
|
|
1
|
+
# @journeeze/sdk
|
|
2
2
|
|
|
3
|
-
|
|
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.
|