@guidelytech/sdk-core 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,158 @@
1
- # Temporary Holding Version
1
+ # @guidelytech/sdk-core
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
+ Framework-independent Guidely browser SDK: loads the published configuration for an environment,
4
+ identifies users, tracks events, evaluates targeting/trigger/frequency rules and renders guides
5
+ through `@guidelytech/guide-runtime`. Framework adapters (`@guidelytech/react`, `@guidelytech/nextjs`,
6
+ `@guidelytech/svelte`, `@guidelytech/sveltekit`) and the script-tag build (`@guidelytech/embed`) wrap this
7
+ package.
8
+
9
+ ```bash
10
+ pnpm add @guidelytech/sdk-core
11
+ ```
12
+
13
+ ```ts
14
+ import { createGuidely } from "@guidelytech/sdk-core";
15
+
16
+ const guidely = createGuidely({
17
+ projectId: "pk_dev_demo_react", // environment public key (not a secret)
18
+ apiUrl: "http://localhost:3000", // defaults to https://api.guidely.app
19
+ environment: "development",
20
+ });
21
+
22
+ await guidely.ready();
23
+ await guidely.identify("user_123", { role: "admin", plan: "pro" }, { id: "acme", traits: { seats: 12 } });
24
+ guidely.track("contact_created", { source: "import" });
25
+ guidely.on("guide:complete", (guide) => console.log("completed", guide.key));
26
+ ```
27
+
28
+ The module is safe to import during SSR (nothing touches `window` at module scope).
29
+ `createGuidely()` throws a friendly error when called on the server.
30
+
31
+ ## Options
32
+
33
+ | Option | Default | Description |
34
+ |---|---|---|
35
+ | `projectId` | — | Environment public key (`pk_dev_…`, `pk_stag_…`, `pk_prod_…`). Required. |
36
+ | `environment` | inferred from key prefix | `development` \| `staging` \| `production`; exposed to targeting (`environment` field). |
37
+ | `apiUrl` | `https://api.guidely.app` | Base URL of the Guidely API. Demos use `http://localhost:3000`. |
38
+ | `debug` | `false` | Log trigger decisions and requests with `console.debug("[Guidely]", …)`. |
39
+ | `analytics` | `true` | Send analytics events. Guides still render when `false`. |
40
+ | `consent` | `"granted"` | `"pending"` buffers events (persisted) until `setConsent("granted")`; `"denied"` disables analytics. |
41
+ | `storage` | `"local"` | `"local"` \| `"session"` \| `"memory"` for identity, cached config, state and queue. |
42
+ | `pollIntervalMs` | `1000` | Idle re-check interval (min 250ms): re-arms `element_visible` triggers whose target appears without navigation and drains the guide queue. |
43
+ | `configRefreshMs` | `300000` | Periodic config refresh (`If-None-Match` → 304 when unchanged). |
44
+ | `maskSelectors` | `[]` | Elements whose text is never captured in authoring picks (merged with the project setting). |
45
+ | `onError` | — | Receives every error the SDK swallows (network, rendering). |
46
+ | `theme` | — | Overrides for the project theme (`primaryColor`, `backgroundColor`, `textColor`, `borderRadius`, `fontFamily`). |
47
+ | `user` | — | `{ id, traits?, account? }` to identify immediately. |
48
+ | `fetch` | global | Custom `fetch` (tests, polyfills). |
49
+ | `disableAuthoring` | `false` | Ignore `?guidely_authoring=` in the URL. |
50
+
51
+ ## API
52
+
53
+ All methods are safe to call at any time; failures are reported through `onError` / the `error`
54
+ event, never thrown into the host application.
55
+
56
+ | Method | Description |
57
+ |---|---|
58
+ | `init(): Promise<void>` / `ready(): Promise<void>` | Load config (cache first, then network), wire listeners, evaluate triggers. Idempotent. Resolves even when the API is unreachable (cached config is used; an `error` event is emitted). |
59
+ | `identify(userId, traits?, account?)` | POST `/sdk/identify`; merges server state (audiences, guide history, checklist progress, submitted surveys). Local state wins when both exist. Re-evaluates triggers (`segment_enter`, targeting). |
60
+ | `setAccount({ id, traits? })` | Sets the account for the current user and re-identifies. |
61
+ | `updateTraits(traits)` | Merges traits and re-identifies. |
62
+ | `reset()` | New anonymous id, cleared user and local state, new session; triggers re-evaluated. |
63
+ | `track(name, properties?)` | Custom event. Drives `event` triggers, `advanceOn: "event"` steps and checklist tasks (`completion: "event"`). Keys matching `SENSITIVE_PROPERTY_PATTERN` are stripped. |
64
+ | `page(url?)` | Records a `page_view` and re-evaluates triggers. Called automatically on SPA navigation (History API patch + `popstate` + `hashchange`); adapters call it explicitly too. |
65
+ | `setConsent("granted" \| "denied" \| "pending")` | Runtime consent changes. |
66
+ | `startGuide(idOrKey, { stepId? })` | Starts a guide regardless of targeting/frequency, replacing any active guide. Checklists open their panel. Returns `false` for unknown guides. |
67
+ | `stopGuide()` | Removes the active guide silently (no dismiss event). |
68
+ | `dismissGuide()` | Dismisses the active guide (records `guide_dismissed`). |
69
+ | `getActiveGuide(): PublishedGuide \| null` | The exclusive guide currently showing. |
70
+ | `getGuides(): readonly PublishedGuide[]` | Published guides for this environment. |
71
+ | `isGuideCompleted(idOrKey)` | From merged local + server state. |
72
+ | `getChecklistProgress(idOrKey)` | `{ completedTaskIds, completedAt, dismissedAt }`. |
73
+ | `completeChecklistTask(idOrKey, taskId)` | Manual completion; syncs to `/sdk/checklists/:id/progress`. |
74
+ | `openChecklist(idOrKey)` / `closeChecklist(idOrKey)` | Show/hide the checklist panel (re-renders a dismissed checklist). |
75
+ | `openResourceCenter()` / `closeResourceCenter()` | Toggle the resource center panel. |
76
+ | `refresh()` | Re-fetch config now. |
77
+ | `on(event, cb): () => void` / `off(event, cb)` | Subscribe to events (below). |
78
+ | `getContext()` | The `TargetingContext` used for rule evaluation (debugging). |
79
+ | `getState()` | Debug snapshot: ready flag, ids, config version, active/queued guides, widgets, state, pending events. |
80
+ | `destroy()` | Full cleanup: guides, widgets, listeners, timers, the `#guidely-root` host; flushes pending events via `sendBeacon`. A new client can be created afterwards. |
81
+ | `GuidelyClient.init(options)` / `GuidelyClient.get()` | Singleton helpers used by the embed (`window.Guidely.init/get`). |
82
+
83
+ ### Events
84
+
85
+ `ready(config)`, `config(config)`, `guide:start(guide)`, `guide:step({ guide, stepId, stepIndex })`,
86
+ `guide:complete(guide)`, `guide:dismiss(guide)`, `guide:skip(guide)`,
87
+ `checklist:task({ guide, task, progress })`, `checklist:complete(guide)`,
88
+ `survey:submit({ guide, answers, responseId })`, `resource-center:open()`, `error(Error)`,
89
+ `track(sdkEvent)`, `identify({ userId, anonymousId })`, `authoring({ active })`.
90
+
91
+ ## Behaviour
92
+
93
+ * **Config**: `GET /api/v1/sdk/config?key=…` with `If-None-Match`; the last good response is cached
94
+ in storage (`guidely:config:<key>`) and used when offline. Refreshed every `configRefreshMs` and on `refresh()`.
95
+ * **Events**: queued in memory + storage (max 200, oldest dropped), deduped by `eventId`, flushed in
96
+ batches of ≤50 every 2s, on navigation and on `visibilitychange`/`pagehide` (via `sendBeacon`,
97
+ falling back to `fetch`). Failed batches retry with exponential backoff (1s → 30s, 5 attempts)
98
+ and stay queued afterwards.
99
+ * **Triggers**: `page_load`, `url_match`, `event`, `element_click` (delegated capture listener),
100
+ `element_visible` (IntersectionObserver), `delay`, `after_guide`, `segment_enter`, `manual`.
101
+ Filtered by `urlPatterns`, `trigger.urlPattern`, targeting rules (`rolloutSalt = guide.id`),
102
+ frequency (`once`, `once_per_session`, `every_time`, `interval`, `maxImpressions`,
103
+ `cooldownHours`, `reshowAfterDismiss`) and survey `startAt`/`endAt`.
104
+ * **Exclusivity**: one flow/modal/tooltip/survey at a time, lowest `priority` first,
105
+ `mutuallyExclusiveWith` respected; banners, checklists, hotspot-only guides and the resource
106
+ center coexist. Remaining eligible guides queue and show after the active one ends (page-scoped
107
+ triggers are dropped if the page changed).
108
+ * **Multi-page flows**: `step.url` steps wait for navigation; the active step is persisted in
109
+ `sessionStorage` (`guidely:active`) so full page loads resume the guide.
110
+ * **Server sync** (fire-and-forget with retries): `POST /sdk/guide-state`,
111
+ `POST /sdk/checklists/:id/progress`, `POST /sdk/surveys/:id/responses`,
112
+ `GET /sdk/resource-center/search?q=`.
113
+
114
+ ## Authoring mode
115
+
116
+ When the URL contains `?guidely_authoring=<token>` the SDK:
117
+
118
+ 1. Strips the parameter (`history.replaceState`) and stores `{ token }` in
119
+ `sessionStorage["guidely:authoring"]` so SPA navigations keep authoring active.
120
+ 2. Calls `GET /sdk/authoring/session?token=` and, when valid, starts the picker from
121
+ `@guidelytech/guide-runtime` (toolbar, hover outline, click-to-pick) and a 15s heartbeat
122
+ (`POST /sdk/authoring/session/heartbeat?token=`).
123
+ 3. Sends each pick as `window.opener.postMessage({ type: "guidely:pick", sessionId, pick }, dashboardOrigin)`
124
+ **and** `POST /sdk/authoring/session/picks?token=`.
125
+
126
+ Messages accepted from `dashboardOrigin` (`event.origin` checked):
127
+
128
+ | `type` | Payload | Effect |
129
+ |---|---|---|
130
+ | `guidely:preview-step` | `{ step, theme?, appearance? }` | Renders the draft step live (target fallback → modal). |
131
+ | `guidely:preview-guide` | `{ guide: { id?, key?, name?, kind?, version?, config } }` | Runs the draft guide. |
132
+ | `guidely:highlight` | `{ selectors }` | Outlines matches, replies `guidely:highlight-result { counts, resolved }`. |
133
+ | `guidely:stop-preview` | — | Removes the preview. |
134
+ | `guidely:ping` | — | Replies `guidely:pong { url }`. |
135
+ | `guidely:exit` | — | Leaves authoring mode. |
136
+
137
+ Messages posted to the opener: `guidely:authoring-ready { sessionId, url }`, `guidely:pick`,
138
+ `guidely:highlight-result`, `guidely:preview-ended { reason }`, `guidely:pong`, `guidely:exit`.
139
+
140
+ ## Storage keys
141
+
142
+ `guidely:anon`, `guidely:seen`, `guidely:config:<key>`, `guidely:etag:<key>`, `guidely:state:<key>`,
143
+ `guidely:user:<key>`, `guidely:queue:<key>` (local/session storage per `storage` option);
144
+ `guidely:session`, `guidely:active`, `guidely:authoring` (sessionStorage).
145
+
146
+ ## Bundle size
147
+
148
+ `pnpm --filter @guidelytech/sdk-core size` builds a minified ESM bundle and prints gzip sizes:
149
+
150
+ | Bundle | minified | min+gz |
151
+ |---|---|---|
152
+ | sdk-core + guide-runtime + floating-ui + `@guidelytech/shared/runtime` (everything an app ships) | 127 kB | **39.4 kB** |
153
+ | Scout messenger chunk (`@guidelytech/guide-runtime/messenger`, loaded on demand) | 26 kB | 8.8 kB |
154
+ | script-tag build `guidely.js` (authoring schemas and messenger inlined) | 244 kB | ~71 kB |
155
+
156
+ The SDK imports only the zod-free `@guidelytech/shared/runtime` entry; the Zod schemas are loaded
157
+ lazily and only when the page enters authoring mode. Budget: 45 kB min+gz for the npm runtime and
158
+ 15 kB for the messenger chunk.