@apex-inc/react 0.3.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.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared types for the @apex-inc/react package.
3
+ *
4
+ * Intentionally mirrors `EndUserCommPreferences` from the app source
5
+ * tree without importing it — the package has no dependency on the
6
+ * app workspace.
7
+ */
8
+ export {};
@@ -0,0 +1,189 @@
1
+ /**
2
+ * `useApexPreferences` — headless data hook backing the
3
+ * `<ApexPreferenceCenter>` component.
4
+ *
5
+ * Customers with their own design system import this hook directly
6
+ * and render their own controls. The default `<ApexPreferenceCenter>`
7
+ * built on top of this hook ships as the "drop a tag, get a working
8
+ * surface" path; the hook is the "I'll style it myself" path.
9
+ *
10
+ * The hook never throws — all errors land on `state.error` so the
11
+ * caller can render an inline message instead of an error boundary
12
+ * fallback. This matches the dashboard's own preference page UX.
13
+ */
14
+ import { useCallback, useEffect, useState } from "react";
15
+ const DEFAULT_API = "https://app.apex.inc";
16
+ function decodeHmacEuid(token) {
17
+ // The Apex token shape is `<base64url-payload>.<base64url-sig>`.
18
+ // We only read the payload; the server re-verifies the signature
19
+ // on every call, so we never trust the decoded values on the
20
+ // client beyond using them as fetch parameters.
21
+ const parts = token.split(".");
22
+ if (parts.length !== 2)
23
+ return {};
24
+ try {
25
+ const padded = parts[0]
26
+ .replace(/-/g, "+")
27
+ .replace(/_/g, "/")
28
+ .padEnd(parts[0].length + ((4 - (parts[0].length % 4)) % 4), "=");
29
+ const json = atob(padded);
30
+ const obj = JSON.parse(json);
31
+ return { euid: obj.euid, pk: obj.pk };
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ }
37
+ export function useApexPreferences(options) {
38
+ const apiBaseUrl = (options.apiBaseUrl ?? DEFAULT_API).replace(/\/$/, "");
39
+ // Resolve identity once. With token auth we crack open the payload
40
+ // to read the workspaceKey + endUserId; with cookie auth we require
41
+ // both as explicit props.
42
+ const tokenAuth = "token" in options.endUserAuth ? options.endUserAuth : null;
43
+ const decoded = tokenAuth ? decodeHmacEuid(tokenAuth.token) : null;
44
+ const endUserId = options.endUserId ?? decoded?.euid;
45
+ const workspaceKey = options.workspaceKey ?? decoded?.pk;
46
+ const [state, setState] = useState({
47
+ prefs: null,
48
+ loading: true,
49
+ saving: false,
50
+ saved: false,
51
+ error: null,
52
+ });
53
+ const authHeaders = useCallback(() => {
54
+ const headers = {
55
+ "Content-Type": "application/json",
56
+ };
57
+ if (tokenAuth) {
58
+ headers["Authorization"] = `Bearer ${tokenAuth.token}`;
59
+ }
60
+ if (workspaceKey) {
61
+ headers["x-workspace-key"] = workspaceKey;
62
+ }
63
+ return headers;
64
+ }, [tokenAuth, workspaceKey]);
65
+ const fetchOpts = useCallback(() => {
66
+ const init = { headers: authHeaders() };
67
+ if (!tokenAuth) {
68
+ // Cookie-auth callers need credentials forwarded to pick up the
69
+ // Apex dashboard's `apex_session` cookie.
70
+ init.credentials = "include";
71
+ }
72
+ return init;
73
+ }, [authHeaders, tokenAuth]);
74
+ const refresh = useCallback(async () => {
75
+ if (!endUserId) {
76
+ setState((s) => ({
77
+ ...s,
78
+ loading: false,
79
+ error: "endUserId not available — pass it explicitly or use a token-based auth.",
80
+ }));
81
+ return;
82
+ }
83
+ setState((s) => ({ ...s, loading: true, error: null }));
84
+ try {
85
+ const url = new URL(`/api/communications/preferences/${encodeURIComponent(endUserId)}`, apiBaseUrl);
86
+ if (workspaceKey)
87
+ url.searchParams.set("workspaceKey", workspaceKey);
88
+ const res = await fetch(url.toString(), fetchOpts());
89
+ if (!res.ok) {
90
+ throw new Error(`HTTP ${res.status}`);
91
+ }
92
+ const prefs = (await res.json());
93
+ setState((s) => ({ ...s, prefs, loading: false }));
94
+ }
95
+ catch (err) {
96
+ setState((s) => ({
97
+ ...s,
98
+ loading: false,
99
+ error: err instanceof Error
100
+ ? err.message
101
+ : "Failed to load preferences",
102
+ }));
103
+ }
104
+ }, [apiBaseUrl, endUserId, fetchOpts, workspaceKey]);
105
+ useEffect(() => {
106
+ void refresh();
107
+ }, [refresh]);
108
+ const save = useCallback(async (patch) => {
109
+ if (!endUserId)
110
+ return;
111
+ setState((s) => ({ ...s, saving: true, saved: false, error: null }));
112
+ try {
113
+ const url = new URL(`/api/communications/preferences/${encodeURIComponent(endUserId)}`, apiBaseUrl);
114
+ if (workspaceKey)
115
+ url.searchParams.set("workspaceKey", workspaceKey);
116
+ const res = await fetch(url.toString(), {
117
+ method: "PATCH",
118
+ ...fetchOpts(),
119
+ body: JSON.stringify(patch),
120
+ });
121
+ if (!res.ok) {
122
+ throw new Error(`HTTP ${res.status}`);
123
+ }
124
+ const saved = (await res.json());
125
+ setState((s) => ({ ...s, prefs: saved, saving: false, saved: true }));
126
+ // Clear the "saved!" badge after 2s — matches the dashboard.
127
+ setTimeout(() => {
128
+ setState((s) => ({ ...s, saved: false }));
129
+ }, 2000);
130
+ }
131
+ catch (err) {
132
+ setState((s) => ({
133
+ ...s,
134
+ saving: false,
135
+ error: err instanceof Error
136
+ ? err.message
137
+ : "Failed to save preferences",
138
+ }));
139
+ }
140
+ }, [apiBaseUrl, endUserId, fetchOpts, workspaceKey]);
141
+ const setGlobalOptOut = useCallback(async (value) => {
142
+ if (!state.prefs)
143
+ return;
144
+ const next = { ...state.prefs, globalOptOut: value };
145
+ setState((s) => ({ ...s, prefs: next }));
146
+ await save({ globalOptOut: value });
147
+ }, [save, state.prefs]);
148
+ const setChannel = useCallback(async (channel, enabled) => {
149
+ if (!state.prefs)
150
+ return;
151
+ // Mirror the legacy `in_app_push` alias when toggling `inbox`.
152
+ const channelPatch = {
153
+ [channel]: enabled,
154
+ };
155
+ if (channel === "inbox")
156
+ channelPatch.in_app_push = enabled;
157
+ const next = {
158
+ ...state.prefs,
159
+ channelPreferences: {
160
+ ...state.prefs.channelPreferences,
161
+ ...channelPatch,
162
+ },
163
+ };
164
+ setState((s) => ({ ...s, prefs: next }));
165
+ await save({ channelPreferences: channelPatch });
166
+ }, [save, state.prefs]);
167
+ const setCommunicationOverride = useCallback(async (communicationId, optedOut) => {
168
+ if (!state.prefs)
169
+ return;
170
+ const next = {
171
+ ...state.prefs,
172
+ communicationOverrides: {
173
+ ...state.prefs.communicationOverrides,
174
+ [communicationId]: { optedOut },
175
+ },
176
+ };
177
+ setState((s) => ({ ...s, prefs: next }));
178
+ await save({
179
+ communicationOverrides: { [communicationId]: { optedOut } },
180
+ });
181
+ }, [save, state.prefs]);
182
+ return {
183
+ ...state,
184
+ refresh,
185
+ setGlobalOptOut,
186
+ setChannel,
187
+ setCommunicationOverride,
188
+ };
189
+ }
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from "react";
2
+ export type Variant = "control" | "variant_b";
3
+ /**
4
+ * Resolution config. `apiBase` defaults to "" (same-origin). When set
5
+ * (along with `workspaceKey` for cross-origin customer apps), requests
6
+ * carry the `x-apex-workspace` header and target the absolute base.
7
+ */
8
+ export interface ApexExperimentConfig {
9
+ /** Apex API base URL, e.g. "https://app.apex.inc". Default "" (same-origin). */
10
+ apiBase?: string;
11
+ /** Workspace key — sent as the `x-apex-workspace` header when present. */
12
+ workspaceKey?: string;
13
+ }
14
+ /**
15
+ * Provides experiment resolution config (apiBase / workspaceKey) to
16
+ * every `useApexVariant` call below it. Optional — per-call overrides
17
+ * and same-origin defaults work without it.
18
+ */
19
+ export declare function ApexProvider(props: {
20
+ apiBase?: string;
21
+ workspaceKey?: string;
22
+ children: ReactNode;
23
+ }): JSX.Element;
24
+ /**
25
+ * React hook for code-level A/B experiments.
26
+ *
27
+ * Returns "control" or "variant_b" based on:
28
+ * 1. Preview mode (?_apex_preview=variant_b&_apex_exp=<id>)
29
+ * 2. Cached assignment in localStorage
30
+ * 3. Server assignment (GET /api/experiments/{id}/assign?source=sdk)
31
+ * 4. Deterministic hash of visitorId + experimentId (fallback)
32
+ *
33
+ * Falls back to "control" on server render and on any error.
34
+ *
35
+ * Config resolution: per-call `overrides` win over `<ApexProvider>`
36
+ * context, which wins over the same-origin default (`apiBase: ""`).
37
+ */
38
+ export declare function useApexVariant(experimentId: string, overrides?: ApexExperimentConfig): Variant;
39
+ /**
40
+ * Clear a cached assignment (useful when an experiment is archived/completed).
41
+ */
42
+ export declare function clearApexVariant(experimentId: string): void;
43
+ /**
44
+ * Check if currently in Apex preview mode for a specific experiment.
45
+ */
46
+ export declare function useApexPreview(experimentId?: string): {
47
+ isPreview: boolean;
48
+ previewVariant: Variant | null;
49
+ };
50
+ //# sourceMappingURL=experiments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"experiments.d.ts","sourceRoot":"","sources":["../src/experiments.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,GAAG,CAAC,OAAO,CAMd;AAiFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,oBAAoB,GAC/B,OAAO,CA0DT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAI3D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG;IACrD,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC,CAaA"}
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ApexProvider = ApexProvider;
5
+ exports.useApexVariant = useApexVariant;
6
+ exports.clearApexVariant = clearApexVariant;
7
+ exports.useApexPreview = useApexPreview;
8
+ /**
9
+ * Code-level A/B experiment hooks for customer React apps.
10
+ *
11
+ * This is the publishable generalization of the app-internal
12
+ * `useApexVariant` hook. The behavior is identical to the first-party
13
+ * web hook — preview params, localStorage cache, visitor cookie,
14
+ * assignment fetch, murmur-hash fallback, and the canonical
15
+ * `experiment_exposure` event (deduped per arm) — but the API base
16
+ * and workspace are configurable so it works from a CUSTOMER's origin,
17
+ * not just same-origin first-party apps.
18
+ *
19
+ * Configure once with `<ApexProvider apiBase="https://app.apex.inc"
20
+ * workspaceKey="ws_…">`, or pass `{ apiBase, workspaceKey }` per call.
21
+ * With no config the hook defaults to same-origin (`apiBase: ""`), so
22
+ * first-party apps keep working unchanged.
23
+ */
24
+ const react_1 = require("react");
25
+ const ApexExperimentContext = (0, react_1.createContext)({});
26
+ /**
27
+ * Provides experiment resolution config (apiBase / workspaceKey) to
28
+ * every `useApexVariant` call below it. Optional — per-call overrides
29
+ * and same-origin defaults work without it.
30
+ */
31
+ function ApexProvider(props) {
32
+ const value = {
33
+ apiBase: props.apiBase,
34
+ workspaceKey: props.workspaceKey,
35
+ };
36
+ return (0, react_1.createElement)(ApexExperimentContext.Provider, { value }, props.children);
37
+ }
38
+ function murmurhash3(key) {
39
+ let h = 0x811c9dc5;
40
+ for (let i = 0; i < key.length; i++) {
41
+ h ^= key.charCodeAt(i);
42
+ h = Math.imul(h, 0x01000193);
43
+ }
44
+ return (h >>> 0) % 100;
45
+ }
46
+ function getVisitorId() {
47
+ if (typeof window === "undefined")
48
+ return "";
49
+ const key = "apex_vid";
50
+ const match = document.cookie.match(new RegExp(`(?:^|; )${key}=([^;]*)`));
51
+ if (match)
52
+ return decodeURIComponent(match[1]);
53
+ const id = crypto.randomUUID();
54
+ const d = new Date();
55
+ d.setTime(d.getTime() + 365 * 86400000);
56
+ document.cookie = `${key}=${encodeURIComponent(id)};expires=${d.toUTCString()};path=/;SameSite=Lax`;
57
+ return id;
58
+ }
59
+ function normalizeBase(apiBase) {
60
+ return (apiBase ?? "").replace(/\/$/, "");
61
+ }
62
+ function workspaceHeaders(workspaceKey) {
63
+ return workspaceKey ? { "x-apex-workspace": workspaceKey } : {};
64
+ }
65
+ /**
66
+ * Fire-once-per-arm exposure tracking. The denominator every experiment
67
+ * surface counts against (canonical `experiment_exposure` event). Deduped
68
+ * per (experiment, variant) for the page session so re-renders don't spam.
69
+ */
70
+ const firedExposures = new Set();
71
+ function fireExposure(experimentId, variant, config) {
72
+ if (typeof window === "undefined")
73
+ return;
74
+ const key = `${experimentId}:${variant}`;
75
+ if (firedExposures.has(key))
76
+ return;
77
+ firedExposures.add(key);
78
+ const visitorId = getVisitorId();
79
+ const base = normalizeBase(config.apiBase);
80
+ try {
81
+ void fetch(`${base}/api/events`, {
82
+ method: "POST",
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ ...workspaceHeaders(config.workspaceKey),
86
+ },
87
+ credentials: "include",
88
+ keepalive: true,
89
+ body: JSON.stringify({
90
+ type: "experiment_exposure",
91
+ visitorId,
92
+ url: window.location.href,
93
+ timestamp: new Date().toISOString(),
94
+ experimentId,
95
+ variant,
96
+ data: { experiment_id: experimentId, variant_key: variant, surface: "web" },
97
+ }),
98
+ }).catch(() => { });
99
+ }
100
+ catch {
101
+ /* exposure tracking is best-effort */
102
+ }
103
+ }
104
+ function getPreviewParams() {
105
+ if (typeof window === "undefined")
106
+ return null;
107
+ const params = new URLSearchParams(window.location.search);
108
+ const variant = params.get("_apex_preview");
109
+ const expId = params.get("_apex_exp");
110
+ if (variant)
111
+ return { variant, expId };
112
+ return null;
113
+ }
114
+ /**
115
+ * React hook for code-level A/B experiments.
116
+ *
117
+ * Returns "control" or "variant_b" based on:
118
+ * 1. Preview mode (?_apex_preview=variant_b&_apex_exp=<id>)
119
+ * 2. Cached assignment in localStorage
120
+ * 3. Server assignment (GET /api/experiments/{id}/assign?source=sdk)
121
+ * 4. Deterministic hash of visitorId + experimentId (fallback)
122
+ *
123
+ * Falls back to "control" on server render and on any error.
124
+ *
125
+ * Config resolution: per-call `overrides` win over `<ApexProvider>`
126
+ * context, which wins over the same-origin default (`apiBase: ""`).
127
+ */
128
+ function useApexVariant(experimentId, overrides) {
129
+ const ctx = (0, react_1.useContext)(ApexExperimentContext);
130
+ const apiBase = overrides?.apiBase ?? ctx.apiBase ?? "";
131
+ const workspaceKey = overrides?.workspaceKey ?? ctx.workspaceKey;
132
+ const config = { apiBase, workspaceKey };
133
+ const [variant, setVariant] = (0, react_1.useState)(() => {
134
+ const preview = getPreviewParams();
135
+ if (preview && (!preview.expId || preview.expId === experimentId)) {
136
+ return preview.variant;
137
+ }
138
+ if (typeof window === "undefined")
139
+ return "control";
140
+ const cached = localStorage.getItem(`apex_var_${experimentId}`);
141
+ if (cached === "control" || cached === "variant_b")
142
+ return cached;
143
+ return "control";
144
+ });
145
+ (0, react_1.useEffect)(() => {
146
+ // Preview mode is the author previewing — not a real exposure.
147
+ const preview = getPreviewParams();
148
+ if (preview && (!preview.expId || preview.expId === experimentId))
149
+ return;
150
+ const cacheKey = `apex_var_${experimentId}`;
151
+ const cached = localStorage.getItem(cacheKey);
152
+ if (cached === "control" || cached === "variant_b") {
153
+ // Already assigned this session — still count the exposure (deduped).
154
+ fireExposure(experimentId, cached, config);
155
+ return;
156
+ }
157
+ const visitorId = getVisitorId();
158
+ if (!visitorId)
159
+ return;
160
+ const base = normalizeBase(apiBase);
161
+ fetch(`${base}/api/experiments/${experimentId}/assign?source=sdk`, {
162
+ credentials: "include",
163
+ headers: workspaceHeaders(workspaceKey),
164
+ })
165
+ .then((r) => {
166
+ if (!r.ok)
167
+ throw new Error(`${r.status}`);
168
+ return r.json();
169
+ })
170
+ .then((data) => {
171
+ localStorage.setItem(cacheKey, data.variant);
172
+ setVariant(data.variant);
173
+ fireExposure(experimentId, data.variant, config);
174
+ })
175
+ .catch(() => {
176
+ const bucket = murmurhash3(visitorId + experimentId);
177
+ const fallback = bucket < 50 ? "control" : "variant_b";
178
+ localStorage.setItem(cacheKey, fallback);
179
+ setVariant(fallback);
180
+ fireExposure(experimentId, fallback, config);
181
+ });
182
+ // eslint-disable-next-line react-hooks/exhaustive-deps
183
+ }, [experimentId, apiBase, workspaceKey]);
184
+ return variant;
185
+ }
186
+ /**
187
+ * Clear a cached assignment (useful when an experiment is archived/completed).
188
+ */
189
+ function clearApexVariant(experimentId) {
190
+ if (typeof window !== "undefined") {
191
+ localStorage.removeItem(`apex_var_${experimentId}`);
192
+ }
193
+ }
194
+ /**
195
+ * Check if currently in Apex preview mode for a specific experiment.
196
+ */
197
+ function useApexPreview(experimentId) {
198
+ const [state] = (0, react_1.useState)(() => {
199
+ const preview = getPreviewParams();
200
+ if (preview && (!experimentId || !preview.expId || preview.expId === experimentId)) {
201
+ return { isPreview: true, previewVariant: preview.variant };
202
+ }
203
+ return { isPreview: false, previewVariant: null };
204
+ });
205
+ return state;
206
+ }
207
+ //# sourceMappingURL=experiments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"experiments.js","sourceRoot":"","sources":["../src/experiments.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @apex-inc/react — embeddable React components for Apex
3
+ * communication surfaces.
4
+ *
5
+ * MVP scope (v0.1):
6
+ * - <ApexPreferenceCenter> — styled + headless variants.
7
+ * - useApexPreferences — headless hook for full-custom UIs.
8
+ *
9
+ * Deferred to v0.2 (gated on design-partner demand):
10
+ * - <ApexInbox> embeddable bell + drawer
11
+ * - useApexWebPushSubscription
12
+ * - Customer service-worker template
13
+ *
14
+ * Design rationale: every product on the planet needs a preferences
15
+ * UI; only some need an embedded inbox. Shipping the universal piece
16
+ * first lets us validate demand for the channel-specific pieces.
17
+ */
18
+ export { ApexPreferenceCenter } from "./ApexPreferenceCenter";
19
+ export type { ApexPreferenceCenterProps } from "./ApexPreferenceCenter";
20
+ export { useApexPreferences, type UseApexPreferencesOptions, type UseApexPreferencesState, type UseApexPreferencesActions, type UseApexPreferencesResult, } from "./useApexPreferences";
21
+ export type { ApexChannel, ApexChannelRead, ApexCommPreferences, ApexEndUserAuth, ApexThemeTokens, } from "./types";
22
+ export { ApexProvider, useApexVariant, useApexPreview, clearApexVariant, type Variant, type ApexExperimentConfig, } from "./experiments";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,KAAK,OAAO,EACZ,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /**
3
+ * @apex-inc/react — embeddable React components for Apex
4
+ * communication surfaces.
5
+ *
6
+ * MVP scope (v0.1):
7
+ * - <ApexPreferenceCenter> — styled + headless variants.
8
+ * - useApexPreferences — headless hook for full-custom UIs.
9
+ *
10
+ * Deferred to v0.2 (gated on design-partner demand):
11
+ * - <ApexInbox> embeddable bell + drawer
12
+ * - useApexWebPushSubscription
13
+ * - Customer service-worker template
14
+ *
15
+ * Design rationale: every product on the planet needs a preferences
16
+ * UI; only some need an embedded inbox. Shipping the universal piece
17
+ * first lets us validate demand for the channel-specific pieces.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.clearApexVariant = exports.useApexPreview = exports.useApexVariant = exports.ApexProvider = exports.useApexPreferences = exports.ApexPreferenceCenter = void 0;
21
+ var ApexPreferenceCenter_1 = require("./ApexPreferenceCenter");
22
+ Object.defineProperty(exports, "ApexPreferenceCenter", { enumerable: true, get: function () { return ApexPreferenceCenter_1.ApexPreferenceCenter; } });
23
+ var useApexPreferences_1 = require("./useApexPreferences");
24
+ Object.defineProperty(exports, "useApexPreferences", { enumerable: true, get: function () { return useApexPreferences_1.useApexPreferences; } });
25
+ // ─── Code-level A/B experiments ───────────────────────────────────────────
26
+ var experiments_1 = require("./experiments");
27
+ Object.defineProperty(exports, "ApexProvider", { enumerable: true, get: function () { return experiments_1.ApexProvider; } });
28
+ Object.defineProperty(exports, "useApexVariant", { enumerable: true, get: function () { return experiments_1.useApexVariant; } });
29
+ Object.defineProperty(exports, "useApexPreview", { enumerable: true, get: function () { return experiments_1.useApexPreview; } });
30
+ Object.defineProperty(exports, "clearApexVariant", { enumerable: true, get: function () { return experiments_1.clearApexVariant; } });
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAEH,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAE7B,2DAM8B;AAL5B,wHAAA,kBAAkB,OAAA;AAcpB,6EAA6E;AAC7E,6CAOuB;AANrB,2GAAA,YAAY,OAAA;AACZ,6GAAA,cAAc,OAAA;AACd,6GAAA,cAAc,OAAA;AACd,+GAAA,gBAAgB,OAAA"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared types for the @apex-inc/react package.
3
+ *
4
+ * Intentionally mirrors `EndUserCommPreferences` from the app source
5
+ * tree without importing it — the package has no dependency on the
6
+ * app workspace.
7
+ */
8
+ export type ApexChannel = "email" | "inbox" | "web_push" | "mobile_push";
9
+ /**
10
+ * The legacy `in_app_push` channel still appears on records authored
11
+ * before DFC-007. Treated as an alias for `inbox` on read.
12
+ */
13
+ export type ApexChannelRead = ApexChannel | "in_app_push";
14
+ export interface ApexCommPreferences {
15
+ endUserId: string;
16
+ workspaceKey: string;
17
+ globalOptOut: boolean;
18
+ channelPreferences: Partial<Record<ApexChannelRead, boolean>>;
19
+ communicationOverrides: Record<string, {
20
+ optedOut: boolean;
21
+ }>;
22
+ journeyOverrides?: Record<string, {
23
+ optedOut: boolean;
24
+ }>;
25
+ updatedAt: string;
26
+ }
27
+ /**
28
+ * Auth credentials for the embedded preference center.
29
+ *
30
+ * Production integrations always pass `{ token }` — an Apex-signed
31
+ * HMAC token minted server-side via `apex.signEndUserToken(...)` (or
32
+ * the matching helper in the customer's preferred Apex SDK). The
33
+ * token carries `pk` (workspaceKey) and `euid` (endUserId), so the
34
+ * component doesn't need either prop separately.
35
+ *
36
+ * `{ sessionCookie: true }` is reserved for the Apex dashboard's own
37
+ * dogfood path — same-origin callers (us.apex.inc) can rely on the
38
+ * dashboard's Cognito session cookie. Customer apps embedding this
39
+ * component on their own domain must use a token.
40
+ */
41
+ export type ApexEndUserAuth = {
42
+ token: string;
43
+ } | {
44
+ sessionCookie: true;
45
+ };
46
+ export interface ApexThemeTokens {
47
+ /** Accent colour for the active checkbox. Defaults to Apex green (`#00BE7D`). */
48
+ accentColor?: string;
49
+ /** Background colour for the surrounding container. */
50
+ backgroundColor?: string;
51
+ /** Foreground text colour. */
52
+ textColor?: string;
53
+ /** Optional border radius in px. */
54
+ borderRadius?: number;
55
+ /** Font stack. */
56
+ fontFamily?: string;
57
+ }
58
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;AAEzE;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,aAAa,CAAC;AAE1D,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACzD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IAAE,aAAa,EAAE,IAAI,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
package/dist/types.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Shared types for the @apex-inc/react package.
4
+ *
5
+ * Intentionally mirrors `EndUserCommPreferences` from the app source
6
+ * tree without importing it — the package has no dependency on the
7
+ * app workspace.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `useApexPreferences` — headless data hook backing the
3
+ * `<ApexPreferenceCenter>` component.
4
+ *
5
+ * Customers with their own design system import this hook directly
6
+ * and render their own controls. The default `<ApexPreferenceCenter>`
7
+ * built on top of this hook ships as the "drop a tag, get a working
8
+ * surface" path; the hook is the "I'll style it myself" path.
9
+ *
10
+ * The hook never throws — all errors land on `state.error` so the
11
+ * caller can render an inline message instead of an error boundary
12
+ * fallback. This matches the dashboard's own preference page UX.
13
+ */
14
+ import type { ApexChannel, ApexCommPreferences, ApexEndUserAuth } from "./types";
15
+ export interface UseApexPreferencesOptions {
16
+ /** Base URL of the Apex API. Defaults to `https://app.apex.inc`. */
17
+ apiBaseUrl?: string;
18
+ /**
19
+ * Workspace identifier. Required for `{ sessionCookie }` auth; for
20
+ * `{ token }` auth the value comes from the token's `pk` claim and
21
+ * passing it explicitly is optional.
22
+ */
23
+ workspaceKey?: string;
24
+ /**
25
+ * End-user identifier (their id in your system). Required for
26
+ * `{ sessionCookie }` auth; with `{ token }` auth the helper uses
27
+ * the token's `euid` claim.
28
+ */
29
+ endUserId?: string;
30
+ /** Auth credentials. See `ApexEndUserAuth` for the two shapes. */
31
+ endUserAuth: ApexEndUserAuth;
32
+ }
33
+ export interface UseApexPreferencesState {
34
+ prefs: ApexCommPreferences | null;
35
+ loading: boolean;
36
+ saving: boolean;
37
+ saved: boolean;
38
+ error: string | null;
39
+ }
40
+ export interface UseApexPreferencesActions {
41
+ refresh: () => Promise<void>;
42
+ setGlobalOptOut: (value: boolean) => Promise<void>;
43
+ setChannel: (channel: ApexChannel, enabled: boolean) => Promise<void>;
44
+ setCommunicationOverride: (communicationId: string, optedOut: boolean) => Promise<void>;
45
+ }
46
+ export type UseApexPreferencesResult = UseApexPreferencesState & UseApexPreferencesActions;
47
+ export declare function useApexPreferences(options: UseApexPreferencesOptions): UseApexPreferencesResult;
48
+ //# sourceMappingURL=useApexPreferences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useApexPreferences.d.ts","sourceRoot":"","sources":["../src/useApexPreferences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,yBAAyB;IACxC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,WAAW,EAAE,eAAe,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,wBAAwB,EAAE,CACxB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,OAAO,KACd,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,GAC5D,yBAAyB,CAAC;AA2B5B,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,wBAAwB,CA8K1B"}