@wireai/activation 0.9.2 → 0.10.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/AGENTS.md +7 -0
- package/CHANGELOG.md +24 -0
- package/INTEGRATION_PROMPT.md +11 -1
- package/README.md +62 -0
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/analytics/index.js +39 -20
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +38 -21
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{currentSession-Dj0cN3Rg.d.ts → currentSession-D0Vq7_VE.d.ts} +33 -4
- package/dist/{currentSession-CRRQLbAq.d.mts → currentSession-DdDkprpM.d.mts} +33 -4
- package/dist/index.d.mts +116 -3
- package/dist/index.d.ts +116 -3
- package/dist/index.js +155 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +148 -22
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.js +1 -2
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +1 -2
- package/dist/questionnaire/index.mjs.map +1 -1
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/activation/index.ts +24 -0
- package/src/activation/revalidation.ts +88 -0
- package/src/activation/useWireActivation.ts +70 -0
- package/src/activation/wireActivation.ts +156 -0
- package/src/analytics/currentSession.ts +41 -7
- package/src/analytics/eventQueue.ts +9 -28
- package/src/analytics/index.ts +7 -1
- package/src/analytics/reportClientEvent.ts +56 -10
- package/src/index.ts +18 -0
- package/src/questionnaire/transport.ts +1 -9
package/AGENTS.md
CHANGED
|
@@ -123,6 +123,13 @@ write your own fetch.
|
|
|
123
123
|
session. `deviceKey` is the identity that matters. Pass `sessionId` only when you have one.
|
|
124
124
|
- Running a firing experiment? Echo the returned `arm` back as `meta.firing_arm` on the review
|
|
125
125
|
submission so per-arm attribution survives a reweighting.
|
|
126
|
+
- **Fire the gate off an in-app action.** `useWireActivation({ serverUrl, apiKey, deviceKey })`
|
|
127
|
+
(ROOT export) returns `{ track, sessionId, revalidation }`. `await track(name, meta?)` POSTs an
|
|
128
|
+
`app_event` (`question_key=name`) under the current session, resolves `true` on 2xx, and bumps
|
|
129
|
+
`revalidation`; list `revalidation` in the `fetchReviewDecision` / `fetchQuestionnaireDecision`
|
|
130
|
+
effect's deps so the gate re-fetches after the action and can fire. React-free factory:
|
|
131
|
+
`createWireActivation(config)`. This is the kit-owned replacement for hand-rolling
|
|
132
|
+
session-id + await-POST + revalidate.
|
|
126
133
|
|
|
127
134
|
## Gotchas (do not miss)
|
|
128
135
|
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
All notable changes to `@wireai/activation` (formerly `wireai-onboarding`).
|
|
4
4
|
Historical entries below the rename keep the old package name on purpose.
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
### Added: `wire.track` + `useWireActivation` (fire a gate off an in-app action)
|
|
9
|
+
|
|
10
|
+
A kit-owned event-trigger surface so a consumer stops hand-rolling the session-id + await-POST +
|
|
11
|
+
revalidate dance to make a review or questionnaire fire off an arbitrary in-app action (the
|
|
12
|
+
workaround Morrow shipped). All additive; no existing API changed.
|
|
13
|
+
|
|
14
|
+
- **`useWireActivation({ serverUrl, apiKey, deviceKey?, ... })`** (root export) returns
|
|
15
|
+
`{ track, sessionId, revalidation }`. `createWireActivation(config)` is the React-free factory
|
|
16
|
+
it wraps (returns `{ track, sessionId, subscribeRevalidation, getRevalidationVersion }`).
|
|
17
|
+
- **`await track(name, meta?)`** POSTs `event_type='app_event'`, `question_key=name` under the
|
|
18
|
+
current `getCurrentSessionId()` plus `user_context.device_key` (via `reportClientEventAwait` to
|
|
19
|
+
`/v1/events`). Resolves `true` on a 2xx and only then bumps decision revalidation; resolves
|
|
20
|
+
`false` (no bump) on no current session, a blank name, or a failed POST. Never throws.
|
|
21
|
+
- **Revalidation pub/sub** (`useActivationRevalidation`, `bumpActivationRevalidation`,
|
|
22
|
+
`subscribeActivationRevalidation`, `getActivationRevalidationVersion`) is a `globalThis`
|
|
23
|
+
`Symbol.for` singleton, so a `track()` on the root bundle reaches a gate subscribed from the
|
|
24
|
+
`./reviews` / `./questionnaire` subpath under `dist`. List `revalidation` in a
|
|
25
|
+
`fetchReviewDecision` / `fetchQuestionnaireDecision` effect's deps and the gate re-fetches after
|
|
26
|
+
the action, then fires.
|
|
27
|
+
- Docs: new README section "Fire a review or questionnaire from an in-app action", an event-trigger
|
|
28
|
+
step in `INTEGRATION_PROMPT.md`, and entries in `llms.txt` / `AGENTS.md`.
|
|
29
|
+
|
|
6
30
|
## [0.9.1] — 2026-07-17
|
|
7
31
|
|
|
8
32
|
### Fixed: the review-decision wire is now documented as it actually is
|
package/INTEGRATION_PROMPT.md
CHANGED
|
@@ -43,7 +43,17 @@ STEPS (do them in order, stop and ask if a convention is ambiguous):
|
|
|
43
43
|
- Analytics: log via `toAnalyticsEvent(e)` → my logger, using the canonical `wire_onboarding_*` names.
|
|
44
44
|
7. Navigation: mount the screen in my first-run/signup flow; branch to the static flow when the gate
|
|
45
45
|
is off or config is missing.
|
|
46
|
-
8.
|
|
46
|
+
8. Event triggers (only if I asked to fire a review/questionnaire off an in-app action): pick the
|
|
47
|
+
action that should trigger it (e.g. finishing a task, journaling a win) and its key, e.g.
|
|
48
|
+
`journal_done`. On my home/gate screen call
|
|
49
|
+
`const { track, sessionId, revalidation } = useWireActivation({ serverUrl: WIREAI_SERVER_URL, apiKey: WIREAI_API_KEY, appId: APP_ID, deviceKey })`
|
|
50
|
+
from `@wireai/activation`, fetch the gate decision in a `useEffect` keyed on `[revalidation]`
|
|
51
|
+
(`fetchReviewDecision({ serverUrl, apiKey }, { sessionId, deviceKey })` → set it into
|
|
52
|
+
`useReviewGate({ config, decision })`; questionnaires use `fetchQuestionnaireDecision` +
|
|
53
|
+
`useQuestionnaireGate` the same way), and at the action site call `await track("journal_done")`.
|
|
54
|
+
Do NOT hand-roll the session id or the POST; `wire.track` owns both and revalidates the gate on
|
|
55
|
+
a successful 2xx.
|
|
56
|
+
9. Verify: type-check (and lint the changed files); test the flag-off + backend-error paths.
|
|
47
57
|
|
|
48
58
|
Report back: the files you changed (path:line), the typecheck result, and anything you couldn't
|
|
49
59
|
infer about my conventions.
|
package/README.md
CHANGED
|
@@ -874,6 +874,68 @@ The next retention module reuses this exact server-directed shape:
|
|
|
874
874
|
with the client once-gating per announcement id through the same storage. Same seam, no code
|
|
875
875
|
yet.
|
|
876
876
|
|
|
877
|
+
## Fire a review or questionnaire from an in-app action (`wire.track`)
|
|
878
|
+
|
|
879
|
+
The decision seam above re-fetches on mount. But the gate usually sits on the home feed while
|
|
880
|
+
the user goes *elsewhere* to do the thing that should trigger it (journals a win, finishes a
|
|
881
|
+
task). The mount fetch ran before that event existed and never re-runs, so the trigger never
|
|
882
|
+
lands. `wire.track` closes that loop. It posts the action, waits for the server to store it,
|
|
883
|
+
then signals every gate to re-fetch. It is the kit-owned replacement for the session-id,
|
|
884
|
+
await-POST, revalidate dance a host would otherwise hand-roll (`reportAppEvent` is the older
|
|
885
|
+
fire-and-forget path; `wire.track` is the awaitable one that also revalidates).
|
|
886
|
+
|
|
887
|
+
`useWireActivation` is the one import that gives you the whole surface. It takes the SAME
|
|
888
|
+
`serverUrl` / `apiKey` as onboarding (never a second key) and returns `{ track, sessionId,
|
|
889
|
+
revalidation }`:
|
|
890
|
+
|
|
891
|
+
- **`track(name, meta?)`** is awaitable. `name` is the exact string a server firing trigger
|
|
892
|
+
matches on (`question_key`). It POSTs `event_type='app_event'` under the current
|
|
893
|
+
`getCurrentSessionId()` plus a `user_context.device_key`, resolves `true` once the server
|
|
894
|
+
has stored it (2xx) and only then bumps revalidation, and resolves `false` (no bump) when
|
|
895
|
+
there is no current session, a blank name, or the POST fails. It never throws.
|
|
896
|
+
- **`sessionId`** is the current per-open session id (read fresh each render), or `undefined`.
|
|
897
|
+
- **`revalidation`** is a counter that ticks on every successful `track()` from anywhere. List
|
|
898
|
+
it in a decision-fetch effect's deps and the fetch re-runs when it changes.
|
|
899
|
+
|
|
900
|
+
### The `track` then re-fetch then fire wiring
|
|
901
|
+
|
|
902
|
+
No single component closes the loop for you, because the decision fetch lives in your gate's
|
|
903
|
+
host effect. Wire it once: fetch the decision on mount AND whenever `revalidation` ticks, then
|
|
904
|
+
pass the result straight to the gate's `decision` prop. Pass the same `deviceKey` to both the
|
|
905
|
+
hook and the fetch so the server reasons about one device.
|
|
906
|
+
|
|
907
|
+
```tsx
|
|
908
|
+
import { useEffect, useState } from "react";
|
|
909
|
+
import { useWireActivation } from "@wireai/activation";
|
|
910
|
+
import {
|
|
911
|
+
fetchReviewDecision,
|
|
912
|
+
useReviewGate,
|
|
913
|
+
type ReviewDecisionResponse,
|
|
914
|
+
} from "@wireai/activation/reviews";
|
|
915
|
+
|
|
916
|
+
const target = { serverUrl: SERVER_URL, apiKey: TENANT_KEY };
|
|
917
|
+
const { track, sessionId, revalidation } = useWireActivation({ ...target, deviceKey });
|
|
918
|
+
const [decision, setDecision] = useState<ReviewDecisionResponse>();
|
|
919
|
+
|
|
920
|
+
// Re-fetch the server decision on mount AND every time a track() succeeds (revalidation ticks).
|
|
921
|
+
useEffect(() => {
|
|
922
|
+
fetchReviewDecision(target, { sessionId, deviceKey }).then((d) => setDecision(d ?? undefined));
|
|
923
|
+
}, [revalidation]);
|
|
924
|
+
|
|
925
|
+
const gate = useReviewGate({ config: { id: "home", minSessions: 2 }, decision, storage });
|
|
926
|
+
|
|
927
|
+
// …elsewhere, on the triggering action. Awaiting it means the re-fetch sees the event:
|
|
928
|
+
await track("journal_done"); // POST stored → revalidation bumps → effect re-fetches → gate can fire
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
The questionnaire gate uses the identical shape: swap `fetchReviewDecision` / `useReviewGate`
|
|
932
|
+
for `fetchQuestionnaireDecision` / `useQuestionnaireGate` from `@wireai/activation/questionnaire`
|
|
933
|
+
(both take a `decision` prop). One `wire.track` call feeds every subscribed gate.
|
|
934
|
+
|
|
935
|
+
If you are outside React (a service, a saga), `createWireActivation(config)` is the React-free
|
|
936
|
+
factory. It returns `{ track, sessionId, subscribeRevalidation, getRevalidationVersion }`: the
|
|
937
|
+
same `track`, plus the raw pub/sub the hook wraps for you.
|
|
938
|
+
|
|
877
939
|
## Feature controls (per-module kill switches)
|
|
878
940
|
|
|
879
941
|
Every activation surface has a switch you can flip from the dashboard "in case of something":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-Bzb-bcB2.mjs';
|
|
2
|
-
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-
|
|
3
|
-
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as
|
|
2
|
+
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-DdDkprpM.mjs';
|
|
3
|
+
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEventAwait, n as reportClientEvents, o as reportClientEventsAwait, p as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-DdDkprpM.mjs';
|
|
4
4
|
import '../types-CNUqMK0D.mjs';
|
|
5
5
|
import '../types-BKfpdZzX.mjs';
|
|
6
6
|
import '../types-BcmagF6K.mjs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-B31G0Cib.js';
|
|
2
|
-
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-
|
|
3
|
-
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as
|
|
2
|
+
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-D0Vq7_VE.js';
|
|
3
|
+
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEventAwait, n as reportClientEvents, o as reportClientEventsAwait, p as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-D0Vq7_VE.js';
|
|
4
4
|
import '../types-Buj9Lw9t.js';
|
|
5
5
|
import '../types-BKfpdZzX.js';
|
|
6
6
|
import '../types-BcmagF6K.js';
|
package/dist/analytics/index.js
CHANGED
|
@@ -499,22 +499,38 @@ var useScreenTracking = (navigationRef, options = {}) => {
|
|
|
499
499
|
|
|
500
500
|
// src/analytics/reportClientEvent.ts
|
|
501
501
|
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
502
|
-
var
|
|
503
|
-
if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return;
|
|
502
|
+
var buildEventsRequest = (target, events) => {
|
|
503
|
+
if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
|
|
504
504
|
try {
|
|
505
505
|
const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
|
|
506
506
|
const headers = { "Content-Type": "application/json" };
|
|
507
507
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
508
|
+
return { url, init: { method: "POST", headers, body: JSON.stringify({ events }) } };
|
|
509
|
+
} catch {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
var reportClientEvents = (target, events) => {
|
|
514
|
+
try {
|
|
515
|
+
const req = buildEventsRequest(target, events);
|
|
516
|
+
if (!req) return;
|
|
517
|
+
void fetch(req.url, req.init).catch(() => {
|
|
513
518
|
});
|
|
514
519
|
} catch {
|
|
515
520
|
}
|
|
516
521
|
};
|
|
517
522
|
var reportClientEvent = (target, event) => reportClientEvents(target, [event]);
|
|
523
|
+
var reportClientEventsAwait = async (target, events) => {
|
|
524
|
+
try {
|
|
525
|
+
const req = buildEventsRequest(target, events);
|
|
526
|
+
if (!req) return false;
|
|
527
|
+
const res = await fetch(req.url, req.init);
|
|
528
|
+
return Boolean(res && res.ok);
|
|
529
|
+
} catch {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
var reportClientEventAwait = (target, event) => reportClientEventsAwait(target, [event]);
|
|
518
534
|
|
|
519
535
|
// src/analytics/analyticsEvent.ts
|
|
520
536
|
var WIRE_ONBOARDING_EVENTS = {
|
|
@@ -860,9 +876,8 @@ var createEventQueue = (options) => {
|
|
|
860
876
|
} catch {
|
|
861
877
|
}
|
|
862
878
|
})();
|
|
863
|
-
const DROP_STATUSES = /* @__PURE__ */ new Set([400, 404, 413, 422]);
|
|
864
879
|
const postBatch = async (events) => {
|
|
865
|
-
if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return
|
|
880
|
+
if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
|
|
866
881
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
867
882
|
const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), 15e3);
|
|
868
883
|
try {
|
|
@@ -875,12 +890,9 @@ var createEventQueue = (options) => {
|
|
|
875
890
|
body: JSON.stringify({ events }),
|
|
876
891
|
signal: controller == null ? void 0 : controller.signal
|
|
877
892
|
});
|
|
878
|
-
|
|
879
|
-
const status = res == null ? void 0 : res.status;
|
|
880
|
-
if (typeof status === "number" && DROP_STATUSES.has(status)) return "drop";
|
|
881
|
-
return "retry";
|
|
893
|
+
return !!(res && res.ok);
|
|
882
894
|
} catch {
|
|
883
|
-
return
|
|
895
|
+
return false;
|
|
884
896
|
} finally {
|
|
885
897
|
clearTimeout(timer);
|
|
886
898
|
}
|
|
@@ -912,8 +924,8 @@ var createEventQueue = (options) => {
|
|
|
912
924
|
try {
|
|
913
925
|
while (pending.length > 0) {
|
|
914
926
|
const batch = pending.slice(0, batchSize);
|
|
915
|
-
const
|
|
916
|
-
if (
|
|
927
|
+
const ok = await postBatch(batch.map((item) => item.event));
|
|
928
|
+
if (!ok) {
|
|
917
929
|
scheduleRetry();
|
|
918
930
|
return;
|
|
919
931
|
}
|
|
@@ -957,13 +969,18 @@ var createEventQueue = (options) => {
|
|
|
957
969
|
};
|
|
958
970
|
|
|
959
971
|
// src/analytics/currentSession.ts
|
|
960
|
-
var
|
|
972
|
+
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
973
|
+
"@wireai/activation:currentSessionId"
|
|
974
|
+
);
|
|
975
|
+
var globalSlot = globalThis;
|
|
961
976
|
var setCurrentSessionId = (id) => {
|
|
962
|
-
if (typeof id === "string" && id.length > 0)
|
|
977
|
+
if (typeof id === "string" && id.length > 0) {
|
|
978
|
+
globalSlot[CURRENT_SESSION_ID_SLOT] = id;
|
|
979
|
+
}
|
|
963
980
|
};
|
|
964
|
-
var getCurrentSessionId = () =>
|
|
981
|
+
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
965
982
|
var resetCurrentSessionId = () => {
|
|
966
|
-
|
|
983
|
+
globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
|
|
967
984
|
};
|
|
968
985
|
|
|
969
986
|
// src/identity/userIdentity.ts
|
|
@@ -1191,7 +1208,9 @@ exports.getCurrentSessionId = getCurrentSessionId;
|
|
|
1191
1208
|
exports.makeSessionId = makeSessionId;
|
|
1192
1209
|
exports.reportAppEvent = reportAppEvent;
|
|
1193
1210
|
exports.reportClientEvent = reportClientEvent;
|
|
1211
|
+
exports.reportClientEventAwait = reportClientEventAwait;
|
|
1194
1212
|
exports.reportClientEvents = reportClientEvents;
|
|
1213
|
+
exports.reportClientEventsAwait = reportClientEventsAwait;
|
|
1195
1214
|
exports.resetCurrentSessionId = resetCurrentSessionId;
|
|
1196
1215
|
exports.screenTrackingHandler = screenTrackingHandler;
|
|
1197
1216
|
exports.setCurrentSessionId = setCurrentSessionId;
|