@guidelytech/react-native 0.9.0 → 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/README.md +11 -0
- package/dist/index.cjs +441 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +441 -107
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -614,51 +614,108 @@ function isArticleListed(collections, article, member) {
|
|
|
614
614
|
}
|
|
615
615
|
var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
616
616
|
|
|
617
|
-
// ../sdk-core/dist/chunk-
|
|
618
|
-
function readJson(store, key, fallback) {
|
|
619
|
-
const raw = store.get(key);
|
|
620
|
-
if (raw == null) return fallback;
|
|
621
|
-
try {
|
|
622
|
-
return JSON.parse(raw);
|
|
623
|
-
} catch {
|
|
624
|
-
return fallback;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
function writeJson(store, key, value) {
|
|
628
|
-
try {
|
|
629
|
-
store.set(key, JSON.stringify(value));
|
|
630
|
-
} catch {
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
var KEYS = {
|
|
634
|
-
anon: "guidely:anon",
|
|
635
|
-
seen: "guidely:seen",
|
|
636
|
-
config: (key) => `guidely:config:${key}`,
|
|
637
|
-
etag: (key) => `guidely:etag:${key}`,
|
|
638
|
-
state: (key) => `guidely:state:${key}`,
|
|
639
|
-
user: (key) => `guidely:user:${key}`,
|
|
640
|
-
queue: (key) => `guidely:queue:${key}`,
|
|
641
|
-
session: "guidely:session",
|
|
642
|
-
active: "guidely:active",
|
|
643
|
-
authoring: "guidely:authoring",
|
|
644
|
-
/** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */
|
|
645
|
-
capture: "guidely:capture",
|
|
646
|
-
capturePending: "guidely:capture:pending",
|
|
647
|
-
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
648
|
-
conversations: (key) => `guidely:conversations:${key}`,
|
|
649
|
-
/** Announcement ids opened in the messenger's News tab. */
|
|
650
|
-
newsSeen: (key) => `guidely:news:${key}`
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
// ../sdk-core/dist/chunk-AKNE7D5M.js
|
|
617
|
+
// ../sdk-core/dist/chunk-C4WWSF67.js
|
|
654
618
|
var __defProp2 = Object.defineProperty;
|
|
655
619
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
656
620
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
621
|
+
var MAX_RETRY_AFTER_MS = 60 * 60 * 1e3;
|
|
622
|
+
var OFFLINE_WAIT_MS = 3e4;
|
|
623
|
+
var ServerHealth = class {
|
|
624
|
+
constructor(opts = {}) {
|
|
625
|
+
__publicField2(this, "failures", 0);
|
|
626
|
+
__publicField2(this, "openUntil", 0);
|
|
627
|
+
__publicField2(this, "lastOkAt", 0);
|
|
628
|
+
__publicField2(this, "listeners", /* @__PURE__ */ new Set());
|
|
629
|
+
__publicField2(this, "threshold");
|
|
630
|
+
__publicField2(this, "baseMs");
|
|
631
|
+
__publicField2(this, "maxMs");
|
|
632
|
+
__publicField2(this, "now");
|
|
633
|
+
__publicField2(this, "random");
|
|
634
|
+
this.threshold = opts.threshold ?? 3;
|
|
635
|
+
this.baseMs = opts.baseMs ?? 5e3;
|
|
636
|
+
this.maxMs = opts.maxMs ?? 5 * 60 * 1e3;
|
|
637
|
+
this.now = opts.now ?? Date.now;
|
|
638
|
+
this.random = opts.random ?? Math.random;
|
|
639
|
+
}
|
|
640
|
+
/** The server answered (2xx, 3xx, or a 4xx other than 429): it is up. */
|
|
641
|
+
success() {
|
|
642
|
+
const wasDown = this.failures > 0 || this.openUntil > 0;
|
|
643
|
+
this.failures = 0;
|
|
644
|
+
this.openUntil = 0;
|
|
645
|
+
this.lastOkAt = this.now();
|
|
646
|
+
if (wasDown) this.notify();
|
|
647
|
+
}
|
|
648
|
+
/** The request failed or the server is overloaded. `retryAfterMs` comes from a `Retry-After` header. */
|
|
649
|
+
failure(retryAfterMs) {
|
|
650
|
+
this.failures += 1;
|
|
651
|
+
const now = this.now();
|
|
652
|
+
if (retryAfterMs != null && retryAfterMs > 0) {
|
|
653
|
+
this.openUntil = Math.max(this.openUntil, now + Math.min(retryAfterMs, MAX_RETRY_AFTER_MS));
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
if (this.failures < this.threshold) return;
|
|
657
|
+
const cooldown = Math.min(this.maxMs, this.baseMs * 2 ** (this.failures - this.threshold));
|
|
658
|
+
this.openUntil = Math.max(this.openUntil, now + cooldown * (0.8 + this.random() * 0.4));
|
|
659
|
+
}
|
|
660
|
+
/** Milliseconds background traffic should wait before trying again (0: send now). */
|
|
661
|
+
waitMs() {
|
|
662
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return OFFLINE_WAIT_MS;
|
|
663
|
+
return Math.max(0, this.openUntil - this.now());
|
|
664
|
+
}
|
|
665
|
+
canSend() {
|
|
666
|
+
return this.waitMs() === 0;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* The last request got an answer. Unload beacons can't report back, so what they carry is only
|
|
670
|
+
* forgotten while this holds; otherwise it stays queued and is sent again (the server drops duplicates).
|
|
671
|
+
*/
|
|
672
|
+
get reachable() {
|
|
673
|
+
return this.failures === 0 && this.lastOkAt > 0;
|
|
674
|
+
}
|
|
675
|
+
/** The browser came back online: end the cooldown and let queued traffic try straight away. */
|
|
676
|
+
online() {
|
|
677
|
+
this.openUntil = 0;
|
|
678
|
+
this.notify();
|
|
679
|
+
}
|
|
680
|
+
/** Called when the server answers again after failures, or the browser comes back online. */
|
|
681
|
+
onRecover(cb) {
|
|
682
|
+
this.listeners.add(cb);
|
|
683
|
+
return () => this.listeners.delete(cb);
|
|
684
|
+
}
|
|
685
|
+
notify() {
|
|
686
|
+
for (const cb of Array.from(this.listeners)) {
|
|
687
|
+
try {
|
|
688
|
+
cb();
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
695
|
+
if (!value) return null;
|
|
696
|
+
const trimmed = value.trim();
|
|
697
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
698
|
+
const at = Date.parse(trimmed);
|
|
699
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
700
|
+
}
|
|
701
|
+
function isServerFailure(status) {
|
|
702
|
+
return status === 429 || status >= 500;
|
|
703
|
+
}
|
|
704
|
+
var STREAM_RESPONSE_TIMEOUT_MS = 3e4;
|
|
705
|
+
var STREAM_IDLE_TIMEOUT_MS = 6e4;
|
|
657
706
|
var Api = class {
|
|
658
|
-
constructor(baseUrl, key, fetchImpl) {
|
|
707
|
+
constructor(baseUrl, key, fetchImpl, health) {
|
|
659
708
|
__publicField2(this, "baseUrl", baseUrl);
|
|
660
709
|
__publicField2(this, "key", key);
|
|
661
710
|
__publicField2(this, "fetchImpl", fetchImpl);
|
|
711
|
+
__publicField2(this, "health");
|
|
712
|
+
this.health = health ?? new ServerHealth();
|
|
713
|
+
}
|
|
714
|
+
/** Feeds an answer (or a failed request, `null`) into {@link health}. */
|
|
715
|
+
observe(res) {
|
|
716
|
+
if (!res) this.health.failure();
|
|
717
|
+
else if (isServerFailure(res.status)) this.health.failure(parseRetryAfter(res.headers?.get?.("retry-after")));
|
|
718
|
+
else this.health.success();
|
|
662
719
|
}
|
|
663
720
|
url(path, params = {}) {
|
|
664
721
|
const query = Object.entries({ key: this.key, ...params }).filter((e) => e[1] != null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
@@ -678,15 +735,22 @@ var Api = class {
|
|
|
678
735
|
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
679
736
|
const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 1e4) : null;
|
|
680
737
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
738
|
+
let res;
|
|
739
|
+
try {
|
|
740
|
+
res = await f(this.url(path, options.params), {
|
|
741
|
+
method,
|
|
742
|
+
headers,
|
|
743
|
+
body: options.body === void 0 ? void 0 : form ? options.body : JSON.stringify(options.body),
|
|
744
|
+
keepalive: options.keepalive,
|
|
745
|
+
signal: ac?.signal,
|
|
746
|
+
credentials: "omit",
|
|
747
|
+
mode: "cors"
|
|
748
|
+
});
|
|
749
|
+
} catch (err) {
|
|
750
|
+
this.observe(null);
|
|
751
|
+
throw err;
|
|
752
|
+
}
|
|
753
|
+
this.observe(res);
|
|
690
754
|
let data = null;
|
|
691
755
|
if (res.status !== 304 && res.status !== 204) {
|
|
692
756
|
try {
|
|
@@ -712,30 +776,53 @@ var Api = class {
|
|
|
712
776
|
}
|
|
713
777
|
/**
|
|
714
778
|
* POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is
|
|
715
|
-
* returned instead.
|
|
779
|
+
* returned instead. Scout replies can take a while, so there is no overall timeout: the response
|
|
780
|
+
* must start within 30 s and then never go quiet for more than 60 s (the server sends keepalives),
|
|
781
|
+
* otherwise the request is aborted and rejects.
|
|
716
782
|
*/
|
|
717
783
|
async stream(path, body, onEvent, headers = {}) {
|
|
718
784
|
const f = this.fetcher;
|
|
719
785
|
if (!f) throw new Error("fetch is not available");
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
let
|
|
786
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
787
|
+
let timer = null;
|
|
788
|
+
const arm = (ms) => {
|
|
789
|
+
if (!ac) return;
|
|
790
|
+
if (timer) clearTimeout(timer);
|
|
791
|
+
timer = setTimeout(() => ac.abort(), ms);
|
|
792
|
+
};
|
|
793
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS);
|
|
794
|
+
try {
|
|
795
|
+
let res;
|
|
730
796
|
try {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
797
|
+
res = await f(this.url(path), {
|
|
798
|
+
method: "POST",
|
|
799
|
+
headers: { ...headers, "X-Guidely-Key": this.key, Accept: "text/event-stream, application/json", "Content-Type": "application/json" },
|
|
800
|
+
body: JSON.stringify(body),
|
|
801
|
+
signal: ac?.signal,
|
|
802
|
+
credentials: "omit",
|
|
803
|
+
mode: "cors"
|
|
804
|
+
});
|
|
805
|
+
} catch (err) {
|
|
806
|
+
this.observe(null);
|
|
807
|
+
throw err;
|
|
734
808
|
}
|
|
735
|
-
|
|
809
|
+
this.observe(res);
|
|
810
|
+
const type = res.headers?.get?.("content-type") ?? "";
|
|
811
|
+
if (!type.includes("text/event-stream") || !res.body) {
|
|
812
|
+
let data = null;
|
|
813
|
+
try {
|
|
814
|
+
data = await res.json();
|
|
815
|
+
} catch {
|
|
816
|
+
data = null;
|
|
817
|
+
}
|
|
818
|
+
return { status: res.status, ok: res.ok, data, etag: null };
|
|
819
|
+
}
|
|
820
|
+
arm(STREAM_IDLE_TIMEOUT_MS);
|
|
821
|
+
await readEventStream(res.body, onEvent, () => arm(STREAM_IDLE_TIMEOUT_MS));
|
|
822
|
+
return { status: res.status, ok: res.ok, data: null, etag: null };
|
|
823
|
+
} finally {
|
|
824
|
+
if (timer) clearTimeout(timer);
|
|
736
825
|
}
|
|
737
|
-
await readEventStream(res.body, onEvent);
|
|
738
|
-
return { status: res.status, ok: res.ok, data: null, etag: null };
|
|
739
826
|
}
|
|
740
827
|
/** `navigator.sendBeacon` when available (page unload); returns false when it could not be queued. */
|
|
741
828
|
beacon(path, body) {
|
|
@@ -766,7 +853,7 @@ async function withRetry(send, attempts = 3, baseMs = 500) {
|
|
|
766
853
|
function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
|
|
767
854
|
return withRetry(() => api.post(path, body), attempts, baseMs);
|
|
768
855
|
}
|
|
769
|
-
async function readEventStream(body, onEvent) {
|
|
856
|
+
async function readEventStream(body, onEvent, onChunk) {
|
|
770
857
|
const reader = body.getReader();
|
|
771
858
|
const decoder = new TextDecoder();
|
|
772
859
|
let buffer = "";
|
|
@@ -789,6 +876,7 @@ async function readEventStream(body, onEvent) {
|
|
|
789
876
|
};
|
|
790
877
|
for (; ; ) {
|
|
791
878
|
const { done, value } = await reader.read();
|
|
879
|
+
if (value) onChunk?.();
|
|
792
880
|
if (value) buffer += decoder.decode(value, { stream: true }).replace(/\r\n?/g, "\n");
|
|
793
881
|
let sep;
|
|
794
882
|
while ((sep = buffer.indexOf("\n\n")) >= 0) {
|
|
@@ -799,8 +887,44 @@ async function readEventStream(body, onEvent) {
|
|
|
799
887
|
}
|
|
800
888
|
if (buffer.trim()) dispatch(buffer);
|
|
801
889
|
}
|
|
890
|
+
function readJson(store, key, fallback) {
|
|
891
|
+
const raw = store.get(key);
|
|
892
|
+
if (raw == null) return fallback;
|
|
893
|
+
try {
|
|
894
|
+
return JSON.parse(raw);
|
|
895
|
+
} catch {
|
|
896
|
+
return fallback;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function writeJson(store, key, value) {
|
|
900
|
+
try {
|
|
901
|
+
store.set(key, JSON.stringify(value));
|
|
902
|
+
} catch {
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
var KEYS = {
|
|
906
|
+
anon: "guidely:anon",
|
|
907
|
+
seen: "guidely:seen",
|
|
908
|
+
config: (key) => `guidely:config:${key}`,
|
|
909
|
+
etag: (key) => `guidely:etag:${key}`,
|
|
910
|
+
state: (key) => `guidely:state:${key}`,
|
|
911
|
+
user: (key) => `guidely:user:${key}`,
|
|
912
|
+
queue: (key) => `guidely:queue:${key}`,
|
|
913
|
+
/** Survey answers and guide/checklist progress not yet accepted by the server. */
|
|
914
|
+
outbox: (key) => `guidely:outbox:${key}`,
|
|
915
|
+
session: "guidely:session",
|
|
916
|
+
active: "guidely:active",
|
|
917
|
+
authoring: "guidely:authoring",
|
|
918
|
+
/** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */
|
|
919
|
+
capture: "guidely:capture",
|
|
920
|
+
capturePending: "guidely:capture:pending",
|
|
921
|
+
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
922
|
+
conversations: (key) => `guidely:conversations:${key}`,
|
|
923
|
+
/** Announcement ids opened in the messenger's News tab. */
|
|
924
|
+
newsSeen: (key) => `guidely:news:${key}`
|
|
925
|
+
};
|
|
802
926
|
|
|
803
|
-
// ../sdk-core/dist/chunk-
|
|
927
|
+
// ../sdk-core/dist/chunk-GDJB466J.js
|
|
804
928
|
var EventQueue = class {
|
|
805
929
|
constructor(opts) {
|
|
806
930
|
__publicField2(this, "opts", opts);
|
|
@@ -811,17 +935,34 @@ var EventQueue = class {
|
|
|
811
935
|
__publicField2(this, "inflightIds", /* @__PURE__ */ new Set());
|
|
812
936
|
__publicField2(this, "attempt", 0);
|
|
813
937
|
__publicField2(this, "destroyed", false);
|
|
938
|
+
__publicField2(this, "offRecover", null);
|
|
814
939
|
__publicField2(this, "flushIntervalMs");
|
|
815
940
|
__publicField2(this, "maxQueue");
|
|
816
941
|
__publicField2(this, "batchSize");
|
|
817
942
|
__publicField2(this, "maxAttempts");
|
|
818
943
|
this.flushIntervalMs = opts.flushIntervalMs ?? 2e3;
|
|
819
|
-
this.maxQueue = opts.maxQueue ??
|
|
944
|
+
this.maxQueue = opts.maxQueue ?? 500;
|
|
820
945
|
this.batchSize = opts.batchSize ?? 50;
|
|
821
946
|
this.maxAttempts = opts.maxAttempts ?? 5;
|
|
822
947
|
const persisted = readJson(opts.store, opts.storageKey, []);
|
|
823
948
|
if (Array.isArray(persisted)) this.events = persisted.filter((e) => e && typeof e.eventId === "string").slice(-this.maxQueue);
|
|
824
949
|
if (this.events.length) this.schedule();
|
|
950
|
+
this.offRecover = opts.health?.onRecover(() => this.retryNow()) ?? null;
|
|
951
|
+
}
|
|
952
|
+
/** The API is back: drop any pending backoff and send what is queued. */
|
|
953
|
+
retryNow() {
|
|
954
|
+
if (this.destroyed) return;
|
|
955
|
+
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
956
|
+
this.retryTimer = null;
|
|
957
|
+
this.attempt = 0;
|
|
958
|
+
void this.flush();
|
|
959
|
+
}
|
|
960
|
+
retryIn(ms) {
|
|
961
|
+
if (this.retryTimer || this.destroyed) return;
|
|
962
|
+
this.retryTimer = setTimeout(() => {
|
|
963
|
+
this.retryTimer = null;
|
|
964
|
+
void this.flush();
|
|
965
|
+
}, ms);
|
|
825
966
|
}
|
|
826
967
|
get size() {
|
|
827
968
|
return this.events.length;
|
|
@@ -855,6 +996,8 @@ var EventQueue = class {
|
|
|
855
996
|
clearTimeout(this.timer);
|
|
856
997
|
this.timer = null;
|
|
857
998
|
}
|
|
999
|
+
const wait = this.opts.health?.waitMs() ?? 0;
|
|
1000
|
+
if (wait > 0) return this.retryIn(wait);
|
|
858
1001
|
this.inflight = true;
|
|
859
1002
|
const batch = this.events.slice(0, this.batchSize);
|
|
860
1003
|
this.inflightIds = new Set(batch.map((e) => e.eventId));
|
|
@@ -878,26 +1021,20 @@ var EventQueue = class {
|
|
|
878
1021
|
if (this.events.length) this.schedule();
|
|
879
1022
|
return;
|
|
880
1023
|
}
|
|
881
|
-
this.attempt
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
this.retryTimer = setTimeout(() => {
|
|
887
|
-
this.retryTimer = null;
|
|
888
|
-
void this.flush();
|
|
889
|
-
}, backoffMs(this.attempt - 1));
|
|
1024
|
+
this.attempt = Math.min(this.attempt + 1, this.maxAttempts);
|
|
1025
|
+
const delay = this.attempt >= this.maxAttempts ? 6e4 : backoffMs(this.attempt - 1);
|
|
1026
|
+
this.retryIn(Math.max(delay, this.opts.health?.waitMs() ?? 0));
|
|
890
1027
|
}
|
|
891
1028
|
/** Best-effort synchronous flush on unload using `sendBeacon`. */
|
|
892
1029
|
flushSync() {
|
|
893
1030
|
if (this.destroyed || !this.opts.enabled() || !this.opts.beacon) return;
|
|
894
1031
|
const batch = this.events.filter((e) => !this.inflightIds.has(e.eventId)).slice(0, this.batchSize);
|
|
895
1032
|
if (!batch.length) return;
|
|
896
|
-
if (this.opts.beacon(batch)) {
|
|
1033
|
+
if (this.opts.beacon(batch) && (this.opts.health?.reachable ?? true)) {
|
|
897
1034
|
const ids = new Set(batch.map((e) => e.eventId));
|
|
898
1035
|
this.events = this.events.filter((e) => !ids.has(e.eventId));
|
|
899
|
-
this.persist();
|
|
900
1036
|
}
|
|
1037
|
+
this.persist();
|
|
901
1038
|
}
|
|
902
1039
|
clear() {
|
|
903
1040
|
this.events = [];
|
|
@@ -905,6 +1042,7 @@ var EventQueue = class {
|
|
|
905
1042
|
}
|
|
906
1043
|
destroy() {
|
|
907
1044
|
this.destroyed = true;
|
|
1045
|
+
this.offRecover?.();
|
|
908
1046
|
if (this.timer) clearTimeout(this.timer);
|
|
909
1047
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
910
1048
|
this.timer = null;
|
|
@@ -912,6 +1050,88 @@ var EventQueue = class {
|
|
|
912
1050
|
this.persist();
|
|
913
1051
|
}
|
|
914
1052
|
};
|
|
1053
|
+
var Outbox = class {
|
|
1054
|
+
constructor(opts) {
|
|
1055
|
+
__publicField2(this, "opts", opts);
|
|
1056
|
+
__publicField2(this, "items");
|
|
1057
|
+
__publicField2(this, "sending", false);
|
|
1058
|
+
__publicField2(this, "attempt", 0);
|
|
1059
|
+
__publicField2(this, "timer", null);
|
|
1060
|
+
__publicField2(this, "destroyed", false);
|
|
1061
|
+
__publicField2(this, "offRecover");
|
|
1062
|
+
__publicField2(this, "maxItems");
|
|
1063
|
+
__publicField2(this, "maxAgeMs");
|
|
1064
|
+
this.maxItems = opts.maxItems ?? 50;
|
|
1065
|
+
this.maxAgeMs = opts.maxAgeMs ?? 7 * 24 * 60 * 60 * 1e3;
|
|
1066
|
+
const stored = readJson(opts.store, opts.storageKey, []);
|
|
1067
|
+
const cutoff = Date.now() - this.maxAgeMs;
|
|
1068
|
+
this.items = Array.isArray(stored) ? stored.filter((i) => i && typeof i.path === "string" && typeof i.key === "string" && i.createdAt > cutoff).slice(-this.maxItems) : [];
|
|
1069
|
+
this.offRecover = opts.health?.onRecover(() => {
|
|
1070
|
+
this.attempt = 0;
|
|
1071
|
+
this.retryIn(0);
|
|
1072
|
+
}) ?? null;
|
|
1073
|
+
if (this.items.length) this.retryIn(0);
|
|
1074
|
+
}
|
|
1075
|
+
get size() {
|
|
1076
|
+
return this.items.length;
|
|
1077
|
+
}
|
|
1078
|
+
/** Queues `POST path` and sends it now (or as soon as the API is reachable). */
|
|
1079
|
+
add(key, path, body) {
|
|
1080
|
+
if (this.destroyed) return;
|
|
1081
|
+
this.items = this.items.filter((i) => i.key !== key);
|
|
1082
|
+
this.items.push({ key, path, body, createdAt: Date.now() });
|
|
1083
|
+
if (this.items.length > this.maxItems) this.items.splice(0, this.items.length - this.maxItems);
|
|
1084
|
+
this.persist();
|
|
1085
|
+
void this.flush();
|
|
1086
|
+
}
|
|
1087
|
+
persist() {
|
|
1088
|
+
writeJson(this.opts.store, this.opts.storageKey, this.items);
|
|
1089
|
+
}
|
|
1090
|
+
retryIn(ms) {
|
|
1091
|
+
if (this.destroyed) return;
|
|
1092
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1093
|
+
this.timer = setTimeout(() => {
|
|
1094
|
+
this.timer = null;
|
|
1095
|
+
void this.flush();
|
|
1096
|
+
}, ms);
|
|
1097
|
+
}
|
|
1098
|
+
/** Sends queued requests in order, one at a time. */
|
|
1099
|
+
async flush() {
|
|
1100
|
+
if (this.destroyed || this.sending || !this.items.length) return;
|
|
1101
|
+
const wait = this.opts.health?.waitMs() ?? 0;
|
|
1102
|
+
if (wait > 0) return this.retryIn(wait);
|
|
1103
|
+
this.sending = true;
|
|
1104
|
+
try {
|
|
1105
|
+
while (this.items.length && !this.destroyed) {
|
|
1106
|
+
const item = this.items[0];
|
|
1107
|
+
let retry = false;
|
|
1108
|
+
try {
|
|
1109
|
+
const res = await this.opts.api.post(item.path, item.body);
|
|
1110
|
+
retry = !res.ok && (res.status === 429 || res.status >= 500);
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
retry = true;
|
|
1113
|
+
this.opts.onError?.(err);
|
|
1114
|
+
}
|
|
1115
|
+
if (retry) {
|
|
1116
|
+
this.attempt += 1;
|
|
1117
|
+
this.retryIn(Math.max(backoffMs(this.attempt - 1, 1e3, 6e4), this.opts.health?.waitMs() ?? 0));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
this.attempt = 0;
|
|
1121
|
+
this.items = this.items.filter((i) => i !== item);
|
|
1122
|
+
this.persist();
|
|
1123
|
+
}
|
|
1124
|
+
} finally {
|
|
1125
|
+
this.sending = false;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
destroy() {
|
|
1129
|
+
this.destroyed = true;
|
|
1130
|
+
this.offRecover?.();
|
|
1131
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1132
|
+
this.timer = null;
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
915
1135
|
var Emitter = class {
|
|
916
1136
|
constructor(onError) {
|
|
917
1137
|
__publicField2(this, "onError", onError);
|
|
@@ -1467,12 +1687,29 @@ function createSseParser(onEvent) {
|
|
|
1467
1687
|
}
|
|
1468
1688
|
};
|
|
1469
1689
|
}
|
|
1690
|
+
var STREAM_RESPONSE_TIMEOUT_MS2 = 3e4;
|
|
1691
|
+
var STREAM_IDLE_TIMEOUT_MS2 = 6e4;
|
|
1470
1692
|
function xhrStreamPost() {
|
|
1471
1693
|
if (typeof XMLHttpRequest === "undefined") return null;
|
|
1472
1694
|
return (url, headers, body, onChunk) => new Promise((resolve, reject) => {
|
|
1473
1695
|
const xhr = new XMLHttpRequest();
|
|
1474
1696
|
let seen = 0;
|
|
1475
1697
|
let streamed = false;
|
|
1698
|
+
let timer = null;
|
|
1699
|
+
let settled = false;
|
|
1700
|
+
const settle = (fn) => {
|
|
1701
|
+
if (settled) return;
|
|
1702
|
+
settled = true;
|
|
1703
|
+
if (timer) clearTimeout(timer);
|
|
1704
|
+
fn();
|
|
1705
|
+
};
|
|
1706
|
+
const arm = (ms, what) => {
|
|
1707
|
+
if (timer) clearTimeout(timer);
|
|
1708
|
+
timer = setTimeout(() => {
|
|
1709
|
+
settle(() => reject(new Error(`Scout ${what} timed out`)));
|
|
1710
|
+
xhr.abort();
|
|
1711
|
+
}, ms);
|
|
1712
|
+
};
|
|
1476
1713
|
const pump = () => {
|
|
1477
1714
|
const type = xhr.getResponseHeader("content-type") || "";
|
|
1478
1715
|
if (!type.includes("text/event-stream")) return;
|
|
@@ -1485,8 +1722,15 @@ function xhrStreamPost() {
|
|
|
1485
1722
|
};
|
|
1486
1723
|
xhr.open("POST", url);
|
|
1487
1724
|
for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);
|
|
1488
|
-
xhr.
|
|
1489
|
-
|
|
1725
|
+
xhr.onreadystatechange = () => {
|
|
1726
|
+
if (!settled && xhr.readyState >= 2 && xhr.readyState < 4) arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1727
|
+
};
|
|
1728
|
+
xhr.onprogress = () => {
|
|
1729
|
+
if (settled) return;
|
|
1730
|
+
arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1731
|
+
pump();
|
|
1732
|
+
};
|
|
1733
|
+
xhr.onload = () => settle(() => {
|
|
1490
1734
|
pump();
|
|
1491
1735
|
let json = null;
|
|
1492
1736
|
if (!streamed) {
|
|
@@ -1497,27 +1741,74 @@ function xhrStreamPost() {
|
|
|
1497
1741
|
}
|
|
1498
1742
|
}
|
|
1499
1743
|
resolve({ status: xhr.status, json, streamed });
|
|
1500
|
-
};
|
|
1501
|
-
xhr.onerror = () => reject(new Error("Network request failed"));
|
|
1744
|
+
});
|
|
1745
|
+
xhr.onerror = () => settle(() => reject(new Error("Network request failed")));
|
|
1746
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS2, "request");
|
|
1502
1747
|
xhr.send(JSON.stringify(body));
|
|
1503
1748
|
});
|
|
1504
1749
|
}
|
|
1750
|
+
function bodyReader(res) {
|
|
1751
|
+
const body = res.body;
|
|
1752
|
+
const Decoder = globalThis.TextDecoder;
|
|
1753
|
+
return body && typeof body.getReader === "function" && Decoder ? { reader: body.getReader(), decoder: new Decoder() } : null;
|
|
1754
|
+
}
|
|
1505
1755
|
function fetchStreamPost(fetchImpl) {
|
|
1506
1756
|
return async (url, headers, body, onChunk) => {
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1757
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
1758
|
+
let timer = null;
|
|
1759
|
+
let timedOut = "";
|
|
1760
|
+
let fail = () => {
|
|
1761
|
+
};
|
|
1762
|
+
const expired = new Promise((_, reject) => fail = reject);
|
|
1763
|
+
expired.catch(() => {
|
|
1764
|
+
});
|
|
1765
|
+
const arm = (ms, what) => {
|
|
1766
|
+
if (timer) clearTimeout(timer);
|
|
1767
|
+
timer = setTimeout(() => {
|
|
1768
|
+
timedOut = what;
|
|
1769
|
+
ac?.abort();
|
|
1770
|
+
fail(new Error(`Scout ${what} timed out`));
|
|
1771
|
+
}, ms);
|
|
1772
|
+
};
|
|
1773
|
+
const race = (p) => Promise.race([p, expired]);
|
|
1774
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS2, "request");
|
|
1515
1775
|
try {
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1776
|
+
const res = await race(fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body), signal: ac?.signal }));
|
|
1777
|
+
arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1778
|
+
const type = res.headers.get("content-type") || "";
|
|
1779
|
+
const sse = type.includes("text/event-stream");
|
|
1780
|
+
const streaming = sse ? bodyReader(res) : null;
|
|
1781
|
+
if (streaming) {
|
|
1782
|
+
const { reader, decoder } = streaming;
|
|
1783
|
+
for (; ; ) {
|
|
1784
|
+
const { done, value } = await race(reader.read());
|
|
1785
|
+
if (value) {
|
|
1786
|
+
arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1787
|
+
onChunk(decoder.decode(value, { stream: true }));
|
|
1788
|
+
}
|
|
1789
|
+
if (done) break;
|
|
1790
|
+
}
|
|
1791
|
+
const rest = decoder.decode();
|
|
1792
|
+
if (rest) onChunk(rest);
|
|
1793
|
+
return { status: res.status, json: null, streamed: true };
|
|
1794
|
+
}
|
|
1795
|
+
const text = await race(res.text());
|
|
1796
|
+
if (sse) {
|
|
1797
|
+
onChunk(text);
|
|
1798
|
+
return { status: res.status, json: null, streamed: true };
|
|
1799
|
+
}
|
|
1800
|
+
let json = null;
|
|
1801
|
+
try {
|
|
1802
|
+
json = JSON.parse(text || "null");
|
|
1803
|
+
} catch {
|
|
1804
|
+
json = null;
|
|
1805
|
+
}
|
|
1806
|
+
return { status: res.status, json, streamed: false };
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
throw timedOut ? new Error(`Scout ${timedOut} timed out`) : err;
|
|
1809
|
+
} finally {
|
|
1810
|
+
if (timer) clearTimeout(timer);
|
|
1519
1811
|
}
|
|
1520
|
-
return { status: res.status, json, streamed: false };
|
|
1521
1812
|
};
|
|
1522
1813
|
}
|
|
1523
1814
|
var byNewest = (a, b) => Date.parse(b.lastMessageAt) - Date.parse(a.lastMessageAt);
|
|
@@ -1702,7 +1993,15 @@ var Messenger = class {
|
|
|
1702
1993
|
}
|
|
1703
1994
|
});
|
|
1704
1995
|
try {
|
|
1705
|
-
|
|
1996
|
+
let res;
|
|
1997
|
+
try {
|
|
1998
|
+
res = await this.deps.streamPost(this.url(path), headers, payload, (chunk) => parser.feed(chunk));
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
this.deps.health?.failure();
|
|
2001
|
+
throw err;
|
|
2002
|
+
}
|
|
2003
|
+
if (isServerFailure(res.status)) this.deps.health?.failure();
|
|
2004
|
+
else this.deps.health?.success();
|
|
1706
2005
|
parser.end();
|
|
1707
2006
|
if (res.status < 200 || res.status >= 300) throw new Error(`Request failed (${res.status})`);
|
|
1708
2007
|
if (!res.streamed && res.json) {
|
|
@@ -1889,9 +2188,20 @@ var PersistentStore = class {
|
|
|
1889
2188
|
|
|
1890
2189
|
// src/core/client.ts
|
|
1891
2190
|
var SDK_NAME = "@guidelytech/react-native";
|
|
1892
|
-
var SDK_VERSION = "0.
|
|
2191
|
+
var SDK_VERSION = "0.10.0";
|
|
1893
2192
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
1894
2193
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2194
|
+
var CACHED_START_MS = 1500;
|
|
2195
|
+
function settlesWithin(promise, ms) {
|
|
2196
|
+
return new Promise((resolve) => {
|
|
2197
|
+
const timer = setTimeout(() => resolve(false), ms);
|
|
2198
|
+
const done = () => {
|
|
2199
|
+
clearTimeout(timer);
|
|
2200
|
+
resolve(true);
|
|
2201
|
+
};
|
|
2202
|
+
promise.then(done, done);
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
1895
2205
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1896
2206
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
1897
2207
|
var newSession = () => ({ id: `s_${randomId(16)}`, startedAt: nowIso(), lastActiveAt: nowIso(), shownGuides: [], events: [] });
|
|
@@ -1904,6 +2214,8 @@ var GuidelyClient = class {
|
|
|
1904
2214
|
__publicField(this, "store");
|
|
1905
2215
|
__publicField(this, "emitter");
|
|
1906
2216
|
__publicField(this, "queue", null);
|
|
2217
|
+
/** Survey answers, guide completions/dismissals and checklist progress, kept until the server has them. */
|
|
2218
|
+
__publicField(this, "outbox", null);
|
|
1907
2219
|
__publicField(this, "config", null);
|
|
1908
2220
|
__publicField(this, "configFetchedAt", 0);
|
|
1909
2221
|
__publicField(this, "etag", null);
|
|
@@ -1966,7 +2278,7 @@ var GuidelyClient = class {
|
|
|
1966
2278
|
this.emitter = new Emitter((err) => this.reportError(err));
|
|
1967
2279
|
this.api = new Api(this.options.apiUrl, this.options.projectId, options.fetch);
|
|
1968
2280
|
this.store = new PersistentStore(options.storage ?? null, (err) => this.log("storage error", err));
|
|
1969
|
-
if (!options.storage) this.log("no `storage` passed: identity
|
|
2281
|
+
if (!options.storage) this.log("no `storage` passed: identity, guide history, queued events and survey answers last only for this app session, so whatever could not be sent during a Guidely outage is lost when the app restarts. Pass AsyncStorage to persist them.");
|
|
1970
2282
|
this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
|
|
1971
2283
|
if (options.user) this.user = { ...options.user };
|
|
1972
2284
|
this.flags = this.createFlags(options.flags);
|
|
@@ -2017,7 +2329,7 @@ var GuidelyClient = class {
|
|
|
2017
2329
|
async bootstrap() {
|
|
2018
2330
|
const key = this.options.projectId;
|
|
2019
2331
|
try {
|
|
2020
|
-
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.conversations(key)]);
|
|
2332
|
+
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key)]);
|
|
2021
2333
|
if (this.destroyed) return;
|
|
2022
2334
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2023
2335
|
if (!this.anonymousId) {
|
|
@@ -2037,22 +2349,27 @@ var GuidelyClient = class {
|
|
|
2037
2349
|
store: this.store,
|
|
2038
2350
|
storageKey: KEYS.queue(key),
|
|
2039
2351
|
enabled: () => this.analyticsEnabled(),
|
|
2352
|
+
health: this.api.health,
|
|
2040
2353
|
send: async (events) => {
|
|
2041
2354
|
const res = await this.api.post("/events", { events, sentAt: nowIso(), sdk: { name: SDK_NAME, version: SDK_VERSION } });
|
|
2042
2355
|
return { ok: res.ok, retry: !(res.status >= 400 && res.status < 500 && res.status !== 429) };
|
|
2043
2356
|
},
|
|
2044
2357
|
onError: (err) => this.reportError(err)
|
|
2045
2358
|
});
|
|
2359
|
+
this.outbox = new Outbox({ api: this.api, store: this.store, storageKey: KEYS.outbox(key), health: this.api.health });
|
|
2046
2360
|
this.hydrated = true;
|
|
2047
2361
|
const cached = readJson(this.store, KEYS.config(key), null);
|
|
2048
2362
|
this.etag = this.store.get(KEYS.etag(key));
|
|
2049
2363
|
if (cached && Array.isArray(cached.guides)) this.config = forPlatform(cached, this.bridge.device.platform);
|
|
2050
2364
|
this.syncMessenger();
|
|
2051
2365
|
this.pending.splice(0).forEach((fn) => fn());
|
|
2052
|
-
|
|
2366
|
+
const before = this.config?.configVersion;
|
|
2367
|
+
const fresh = this.fetchConfig();
|
|
2368
|
+
if (!this.config) await fresh;
|
|
2369
|
+
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
2053
2370
|
if (this.destroyed) return;
|
|
2054
2371
|
this.wireTimers();
|
|
2055
|
-
if (this.user?.id) await this.identify(this.user.id, this.user.traits, this.user.account);
|
|
2372
|
+
if (this.user?.id) await settlesWithin(this.identify(this.user.id, this.user.traits, this.user.account), CACHED_START_MS);
|
|
2056
2373
|
if (this.destroyed) return;
|
|
2057
2374
|
this.isReady = true;
|
|
2058
2375
|
if (this.config) this.emitter.emit("ready", this.config);
|
|
@@ -2092,7 +2409,9 @@ var GuidelyClient = class {
|
|
|
2092
2409
|
}
|
|
2093
2410
|
}
|
|
2094
2411
|
wireTimers() {
|
|
2095
|
-
const refresh = setInterval(() =>
|
|
2412
|
+
const refresh = setInterval(() => {
|
|
2413
|
+
if (this.api.health.canSend()) void this.refresh();
|
|
2414
|
+
}, this.options.configRefreshMs);
|
|
2096
2415
|
this.cleanups.push(() => clearInterval(refresh));
|
|
2097
2416
|
const poll = setInterval(() => {
|
|
2098
2417
|
if (!this.isReady || !this.config || this.backgroundedAt !== null) return;
|
|
@@ -2108,6 +2427,11 @@ var GuidelyClient = class {
|
|
|
2108
2427
|
if (this.destroyed) return;
|
|
2109
2428
|
const before = this.config?.configVersion;
|
|
2110
2429
|
await this.fetchConfig();
|
|
2430
|
+
this.applyConfigChange(before);
|
|
2431
|
+
}
|
|
2432
|
+
/** Re-renders after a config change (refresh, or a config that arrived after start). */
|
|
2433
|
+
applyConfigChange(before) {
|
|
2434
|
+
if (this.destroyed || !this.isReady) return;
|
|
2111
2435
|
if (this.config && this.config.configVersion !== before) {
|
|
2112
2436
|
this.notify();
|
|
2113
2437
|
this.evaluate("ready");
|
|
@@ -2121,6 +2445,7 @@ var GuidelyClient = class {
|
|
|
2121
2445
|
this.session.lastActiveAt = nowIso();
|
|
2122
2446
|
this.persistSession();
|
|
2123
2447
|
void this.queue?.flush();
|
|
2448
|
+
void this.outbox?.flush();
|
|
2124
2449
|
void this.store.flush();
|
|
2125
2450
|
void this.flags.flush();
|
|
2126
2451
|
this.messenger?.setForeground(false);
|
|
@@ -2134,7 +2459,7 @@ var GuidelyClient = class {
|
|
|
2134
2459
|
this.firedDelays.clear();
|
|
2135
2460
|
}
|
|
2136
2461
|
void this.flags.refresh();
|
|
2137
|
-
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs) void this.refresh();
|
|
2462
|
+
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
2138
2463
|
else if (this.isReady) this.evaluate("ready");
|
|
2139
2464
|
}
|
|
2140
2465
|
}
|
|
@@ -2160,6 +2485,7 @@ var GuidelyClient = class {
|
|
|
2160
2485
|
this.cleanups.splice(0).forEach((fn) => fn());
|
|
2161
2486
|
void this.queue?.flush();
|
|
2162
2487
|
this.queue?.destroy();
|
|
2488
|
+
this.outbox?.destroy();
|
|
2163
2489
|
this.flags.destroy();
|
|
2164
2490
|
this.emitter.clear();
|
|
2165
2491
|
this.uiListeners.clear();
|
|
@@ -2573,7 +2899,13 @@ var GuidelyClient = class {
|
|
|
2573
2899
|
if (reason === "completed") this.completeChecklistTasksBy("guide_completed", guide);
|
|
2574
2900
|
}
|
|
2575
2901
|
syncGuideState(guideId, update) {
|
|
2576
|
-
|
|
2902
|
+
const body = { anonymousId: this.anonymousId, userId: this.user?.id || null, updates: [{ guideId, ...update }] };
|
|
2903
|
+
if (update.impression) void postWithRetry(this.api, "/guide-state", body);
|
|
2904
|
+
else this.sendDurably(`guide:${guideId}:${update.completed ? "completed" : "dismissed"}:${this.anonymousId}`, "/guide-state", body);
|
|
2905
|
+
}
|
|
2906
|
+
/** Queues a write in the persisted outbox (sent now, or once the API is reachable again). */
|
|
2907
|
+
sendDurably(key, path, body) {
|
|
2908
|
+
this.whenHydrated(() => this.outbox?.add(key, path, body));
|
|
2577
2909
|
}
|
|
2578
2910
|
show(guide, manual, stepId) {
|
|
2579
2911
|
try {
|
|
@@ -2781,7 +3113,7 @@ var GuidelyClient = class {
|
|
|
2781
3113
|
progress.dismissedAt = nowIso();
|
|
2782
3114
|
this.persistState();
|
|
2783
3115
|
this.enqueue("checklist_dismissed", { guideId: guide.id, guideVersion: guide.version });
|
|
2784
|
-
|
|
3116
|
+
this.sendDurably(`checklist:${guide.id}:dismissed:${this.anonymousId}`, `/checklists/${encodeURIComponent(guide.id)}/progress`, { anonymousId: this.anonymousId, userId: this.user?.id || null, guideVersion: guide.version, completedTaskIds: progress.completedTaskIds, dismissed: true });
|
|
2785
3117
|
this.checklists.delete(guide.id);
|
|
2786
3118
|
this.notify();
|
|
2787
3119
|
}
|
|
@@ -2794,7 +3126,7 @@ var GuidelyClient = class {
|
|
|
2794
3126
|
this.persistState();
|
|
2795
3127
|
this.recordEnd(guide, "completed");
|
|
2796
3128
|
this.enqueue("survey_submitted", { guideId: guide.id, guideVersion: guide.version, properties: { responseId } });
|
|
2797
|
-
|
|
3129
|
+
this.sendDurably(`survey:${responseId}`, `/surveys/${encodeURIComponent(guide.id)}/responses`, { anonymousId: this.anonymousId, userId: this.user?.id || null, accountId: this.user?.account?.id || null, guideVersion: guide.version, answers, responseId });
|
|
2798
3130
|
this.emitter.emit("survey:submit", { guide, answers, responseId });
|
|
2799
3131
|
this.emitter.emit("guide:complete", guide);
|
|
2800
3132
|
this.surveyView = { ...this.surveyView, submitted: true };
|
|
@@ -2851,7 +3183,7 @@ var GuidelyClient = class {
|
|
|
2851
3183
|
this.enqueue("checklist_completed", { guideId: guide.id, guideVersion: guide.version });
|
|
2852
3184
|
}
|
|
2853
3185
|
this.persistState();
|
|
2854
|
-
|
|
3186
|
+
this.sendDurably(`checklist:${guide.id}:progress:${this.anonymousId}`, `/checklists/${encodeURIComponent(guide.id)}/progress`, { anonymousId: this.anonymousId, userId: this.user?.id || null, guideVersion: guide.version, completedTaskIds: progress.completedTaskIds, completed: !!progress.completedAt });
|
|
2855
3187
|
this.emitter.emit("checklist:task", { guide, task, progress: this.getChecklistProgress(guide.id) });
|
|
2856
3188
|
if (complete) {
|
|
2857
3189
|
this.emitter.emit("checklist:complete", guide);
|
|
@@ -2988,7 +3320,8 @@ var GuidelyClient = class {
|
|
|
2988
3320
|
},
|
|
2989
3321
|
onMessage: (message, conversation) => this.emitter.emit("conversation:message", { message, conversation }),
|
|
2990
3322
|
onStartGuide: (key) => this.setTimer(() => this.startGuide(key), 0),
|
|
2991
|
-
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err)
|
|
3323
|
+
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err),
|
|
3324
|
+
health: this.api.health
|
|
2992
3325
|
});
|
|
2993
3326
|
this.resourceCenterOpen = false;
|
|
2994
3327
|
}
|
|
@@ -3093,6 +3426,7 @@ var GuidelyClient = class {
|
|
|
3093
3426
|
audienceIds: this.state.audienceIds,
|
|
3094
3427
|
session: this.session,
|
|
3095
3428
|
pendingEvents: this.queue?.size ?? 0,
|
|
3429
|
+
pendingWrites: this.outbox?.size ?? 0,
|
|
3096
3430
|
targets: this.targets.ids(),
|
|
3097
3431
|
flagsReady: this.flags.isReady()
|
|
3098
3432
|
};
|