@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.js
CHANGED
|
@@ -554,51 +554,108 @@ function isArticleListed(collections, article, member) {
|
|
|
554
554
|
}
|
|
555
555
|
var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
556
556
|
|
|
557
|
-
// ../sdk-core/dist/chunk-
|
|
558
|
-
function readJson(store, key, fallback) {
|
|
559
|
-
const raw = store.get(key);
|
|
560
|
-
if (raw == null) return fallback;
|
|
561
|
-
try {
|
|
562
|
-
return JSON.parse(raw);
|
|
563
|
-
} catch {
|
|
564
|
-
return fallback;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function writeJson(store, key, value) {
|
|
568
|
-
try {
|
|
569
|
-
store.set(key, JSON.stringify(value));
|
|
570
|
-
} catch {
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
var KEYS = {
|
|
574
|
-
anon: "guidely:anon",
|
|
575
|
-
seen: "guidely:seen",
|
|
576
|
-
config: (key) => `guidely:config:${key}`,
|
|
577
|
-
etag: (key) => `guidely:etag:${key}`,
|
|
578
|
-
state: (key) => `guidely:state:${key}`,
|
|
579
|
-
user: (key) => `guidely:user:${key}`,
|
|
580
|
-
queue: (key) => `guidely:queue:${key}`,
|
|
581
|
-
session: "guidely:session",
|
|
582
|
-
active: "guidely:active",
|
|
583
|
-
authoring: "guidely:authoring",
|
|
584
|
-
/** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */
|
|
585
|
-
capture: "guidely:capture",
|
|
586
|
-
capturePending: "guidely:capture:pending",
|
|
587
|
-
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
588
|
-
conversations: (key) => `guidely:conversations:${key}`,
|
|
589
|
-
/** Announcement ids opened in the messenger's News tab. */
|
|
590
|
-
newsSeen: (key) => `guidely:news:${key}`
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
// ../sdk-core/dist/chunk-AKNE7D5M.js
|
|
557
|
+
// ../sdk-core/dist/chunk-C4WWSF67.js
|
|
594
558
|
var __defProp2 = Object.defineProperty;
|
|
595
559
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
596
560
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
561
|
+
var MAX_RETRY_AFTER_MS = 60 * 60 * 1e3;
|
|
562
|
+
var OFFLINE_WAIT_MS = 3e4;
|
|
563
|
+
var ServerHealth = class {
|
|
564
|
+
constructor(opts = {}) {
|
|
565
|
+
__publicField2(this, "failures", 0);
|
|
566
|
+
__publicField2(this, "openUntil", 0);
|
|
567
|
+
__publicField2(this, "lastOkAt", 0);
|
|
568
|
+
__publicField2(this, "listeners", /* @__PURE__ */ new Set());
|
|
569
|
+
__publicField2(this, "threshold");
|
|
570
|
+
__publicField2(this, "baseMs");
|
|
571
|
+
__publicField2(this, "maxMs");
|
|
572
|
+
__publicField2(this, "now");
|
|
573
|
+
__publicField2(this, "random");
|
|
574
|
+
this.threshold = opts.threshold ?? 3;
|
|
575
|
+
this.baseMs = opts.baseMs ?? 5e3;
|
|
576
|
+
this.maxMs = opts.maxMs ?? 5 * 60 * 1e3;
|
|
577
|
+
this.now = opts.now ?? Date.now;
|
|
578
|
+
this.random = opts.random ?? Math.random;
|
|
579
|
+
}
|
|
580
|
+
/** The server answered (2xx, 3xx, or a 4xx other than 429): it is up. */
|
|
581
|
+
success() {
|
|
582
|
+
const wasDown = this.failures > 0 || this.openUntil > 0;
|
|
583
|
+
this.failures = 0;
|
|
584
|
+
this.openUntil = 0;
|
|
585
|
+
this.lastOkAt = this.now();
|
|
586
|
+
if (wasDown) this.notify();
|
|
587
|
+
}
|
|
588
|
+
/** The request failed or the server is overloaded. `retryAfterMs` comes from a `Retry-After` header. */
|
|
589
|
+
failure(retryAfterMs) {
|
|
590
|
+
this.failures += 1;
|
|
591
|
+
const now = this.now();
|
|
592
|
+
if (retryAfterMs != null && retryAfterMs > 0) {
|
|
593
|
+
this.openUntil = Math.max(this.openUntil, now + Math.min(retryAfterMs, MAX_RETRY_AFTER_MS));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (this.failures < this.threshold) return;
|
|
597
|
+
const cooldown = Math.min(this.maxMs, this.baseMs * 2 ** (this.failures - this.threshold));
|
|
598
|
+
this.openUntil = Math.max(this.openUntil, now + cooldown * (0.8 + this.random() * 0.4));
|
|
599
|
+
}
|
|
600
|
+
/** Milliseconds background traffic should wait before trying again (0: send now). */
|
|
601
|
+
waitMs() {
|
|
602
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return OFFLINE_WAIT_MS;
|
|
603
|
+
return Math.max(0, this.openUntil - this.now());
|
|
604
|
+
}
|
|
605
|
+
canSend() {
|
|
606
|
+
return this.waitMs() === 0;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* The last request got an answer. Unload beacons can't report back, so what they carry is only
|
|
610
|
+
* forgotten while this holds; otherwise it stays queued and is sent again (the server drops duplicates).
|
|
611
|
+
*/
|
|
612
|
+
get reachable() {
|
|
613
|
+
return this.failures === 0 && this.lastOkAt > 0;
|
|
614
|
+
}
|
|
615
|
+
/** The browser came back online: end the cooldown and let queued traffic try straight away. */
|
|
616
|
+
online() {
|
|
617
|
+
this.openUntil = 0;
|
|
618
|
+
this.notify();
|
|
619
|
+
}
|
|
620
|
+
/** Called when the server answers again after failures, or the browser comes back online. */
|
|
621
|
+
onRecover(cb) {
|
|
622
|
+
this.listeners.add(cb);
|
|
623
|
+
return () => this.listeners.delete(cb);
|
|
624
|
+
}
|
|
625
|
+
notify() {
|
|
626
|
+
for (const cb of Array.from(this.listeners)) {
|
|
627
|
+
try {
|
|
628
|
+
cb();
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
635
|
+
if (!value) return null;
|
|
636
|
+
const trimmed = value.trim();
|
|
637
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
638
|
+
const at = Date.parse(trimmed);
|
|
639
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
640
|
+
}
|
|
641
|
+
function isServerFailure(status) {
|
|
642
|
+
return status === 429 || status >= 500;
|
|
643
|
+
}
|
|
644
|
+
var STREAM_RESPONSE_TIMEOUT_MS = 3e4;
|
|
645
|
+
var STREAM_IDLE_TIMEOUT_MS = 6e4;
|
|
597
646
|
var Api = class {
|
|
598
|
-
constructor(baseUrl, key, fetchImpl) {
|
|
647
|
+
constructor(baseUrl, key, fetchImpl, health) {
|
|
599
648
|
__publicField2(this, "baseUrl", baseUrl);
|
|
600
649
|
__publicField2(this, "key", key);
|
|
601
650
|
__publicField2(this, "fetchImpl", fetchImpl);
|
|
651
|
+
__publicField2(this, "health");
|
|
652
|
+
this.health = health ?? new ServerHealth();
|
|
653
|
+
}
|
|
654
|
+
/** Feeds an answer (or a failed request, `null`) into {@link health}. */
|
|
655
|
+
observe(res) {
|
|
656
|
+
if (!res) this.health.failure();
|
|
657
|
+
else if (isServerFailure(res.status)) this.health.failure(parseRetryAfter(res.headers?.get?.("retry-after")));
|
|
658
|
+
else this.health.success();
|
|
602
659
|
}
|
|
603
660
|
url(path, params = {}) {
|
|
604
661
|
const query = Object.entries({ key: this.key, ...params }).filter((e) => e[1] != null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
@@ -618,15 +675,22 @@ var Api = class {
|
|
|
618
675
|
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
619
676
|
const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 1e4) : null;
|
|
620
677
|
try {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
678
|
+
let res;
|
|
679
|
+
try {
|
|
680
|
+
res = await f(this.url(path, options.params), {
|
|
681
|
+
method,
|
|
682
|
+
headers,
|
|
683
|
+
body: options.body === void 0 ? void 0 : form ? options.body : JSON.stringify(options.body),
|
|
684
|
+
keepalive: options.keepalive,
|
|
685
|
+
signal: ac?.signal,
|
|
686
|
+
credentials: "omit",
|
|
687
|
+
mode: "cors"
|
|
688
|
+
});
|
|
689
|
+
} catch (err) {
|
|
690
|
+
this.observe(null);
|
|
691
|
+
throw err;
|
|
692
|
+
}
|
|
693
|
+
this.observe(res);
|
|
630
694
|
let data = null;
|
|
631
695
|
if (res.status !== 304 && res.status !== 204) {
|
|
632
696
|
try {
|
|
@@ -652,30 +716,53 @@ var Api = class {
|
|
|
652
716
|
}
|
|
653
717
|
/**
|
|
654
718
|
* POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is
|
|
655
|
-
* returned instead.
|
|
719
|
+
* returned instead. Scout replies can take a while, so there is no overall timeout: the response
|
|
720
|
+
* must start within 30 s and then never go quiet for more than 60 s (the server sends keepalives),
|
|
721
|
+
* otherwise the request is aborted and rejects.
|
|
656
722
|
*/
|
|
657
723
|
async stream(path, body, onEvent, headers = {}) {
|
|
658
724
|
const f = this.fetcher;
|
|
659
725
|
if (!f) throw new Error("fetch is not available");
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
let
|
|
726
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
727
|
+
let timer = null;
|
|
728
|
+
const arm = (ms) => {
|
|
729
|
+
if (!ac) return;
|
|
730
|
+
if (timer) clearTimeout(timer);
|
|
731
|
+
timer = setTimeout(() => ac.abort(), ms);
|
|
732
|
+
};
|
|
733
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS);
|
|
734
|
+
try {
|
|
735
|
+
let res;
|
|
670
736
|
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
737
|
+
res = await f(this.url(path), {
|
|
738
|
+
method: "POST",
|
|
739
|
+
headers: { ...headers, "X-Guidely-Key": this.key, Accept: "text/event-stream, application/json", "Content-Type": "application/json" },
|
|
740
|
+
body: JSON.stringify(body),
|
|
741
|
+
signal: ac?.signal,
|
|
742
|
+
credentials: "omit",
|
|
743
|
+
mode: "cors"
|
|
744
|
+
});
|
|
745
|
+
} catch (err) {
|
|
746
|
+
this.observe(null);
|
|
747
|
+
throw err;
|
|
674
748
|
}
|
|
675
|
-
|
|
749
|
+
this.observe(res);
|
|
750
|
+
const type = res.headers?.get?.("content-type") ?? "";
|
|
751
|
+
if (!type.includes("text/event-stream") || !res.body) {
|
|
752
|
+
let data = null;
|
|
753
|
+
try {
|
|
754
|
+
data = await res.json();
|
|
755
|
+
} catch {
|
|
756
|
+
data = null;
|
|
757
|
+
}
|
|
758
|
+
return { status: res.status, ok: res.ok, data, etag: null };
|
|
759
|
+
}
|
|
760
|
+
arm(STREAM_IDLE_TIMEOUT_MS);
|
|
761
|
+
await readEventStream(res.body, onEvent, () => arm(STREAM_IDLE_TIMEOUT_MS));
|
|
762
|
+
return { status: res.status, ok: res.ok, data: null, etag: null };
|
|
763
|
+
} finally {
|
|
764
|
+
if (timer) clearTimeout(timer);
|
|
676
765
|
}
|
|
677
|
-
await readEventStream(res.body, onEvent);
|
|
678
|
-
return { status: res.status, ok: res.ok, data: null, etag: null };
|
|
679
766
|
}
|
|
680
767
|
/** `navigator.sendBeacon` when available (page unload); returns false when it could not be queued. */
|
|
681
768
|
beacon(path, body) {
|
|
@@ -706,7 +793,7 @@ async function withRetry(send, attempts = 3, baseMs = 500) {
|
|
|
706
793
|
function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
|
|
707
794
|
return withRetry(() => api.post(path, body), attempts, baseMs);
|
|
708
795
|
}
|
|
709
|
-
async function readEventStream(body, onEvent) {
|
|
796
|
+
async function readEventStream(body, onEvent, onChunk) {
|
|
710
797
|
const reader = body.getReader();
|
|
711
798
|
const decoder = new TextDecoder();
|
|
712
799
|
let buffer = "";
|
|
@@ -729,6 +816,7 @@ async function readEventStream(body, onEvent) {
|
|
|
729
816
|
};
|
|
730
817
|
for (; ; ) {
|
|
731
818
|
const { done, value } = await reader.read();
|
|
819
|
+
if (value) onChunk?.();
|
|
732
820
|
if (value) buffer += decoder.decode(value, { stream: true }).replace(/\r\n?/g, "\n");
|
|
733
821
|
let sep;
|
|
734
822
|
while ((sep = buffer.indexOf("\n\n")) >= 0) {
|
|
@@ -739,8 +827,44 @@ async function readEventStream(body, onEvent) {
|
|
|
739
827
|
}
|
|
740
828
|
if (buffer.trim()) dispatch(buffer);
|
|
741
829
|
}
|
|
830
|
+
function readJson(store, key, fallback) {
|
|
831
|
+
const raw = store.get(key);
|
|
832
|
+
if (raw == null) return fallback;
|
|
833
|
+
try {
|
|
834
|
+
return JSON.parse(raw);
|
|
835
|
+
} catch {
|
|
836
|
+
return fallback;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function writeJson(store, key, value) {
|
|
840
|
+
try {
|
|
841
|
+
store.set(key, JSON.stringify(value));
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
var KEYS = {
|
|
846
|
+
anon: "guidely:anon",
|
|
847
|
+
seen: "guidely:seen",
|
|
848
|
+
config: (key) => `guidely:config:${key}`,
|
|
849
|
+
etag: (key) => `guidely:etag:${key}`,
|
|
850
|
+
state: (key) => `guidely:state:${key}`,
|
|
851
|
+
user: (key) => `guidely:user:${key}`,
|
|
852
|
+
queue: (key) => `guidely:queue:${key}`,
|
|
853
|
+
/** Survey answers and guide/checklist progress not yet accepted by the server. */
|
|
854
|
+
outbox: (key) => `guidely:outbox:${key}`,
|
|
855
|
+
session: "guidely:session",
|
|
856
|
+
active: "guidely:active",
|
|
857
|
+
authoring: "guidely:authoring",
|
|
858
|
+
/** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */
|
|
859
|
+
capture: "guidely:capture",
|
|
860
|
+
capturePending: "guidely:capture:pending",
|
|
861
|
+
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
862
|
+
conversations: (key) => `guidely:conversations:${key}`,
|
|
863
|
+
/** Announcement ids opened in the messenger's News tab. */
|
|
864
|
+
newsSeen: (key) => `guidely:news:${key}`
|
|
865
|
+
};
|
|
742
866
|
|
|
743
|
-
// ../sdk-core/dist/chunk-
|
|
867
|
+
// ../sdk-core/dist/chunk-GDJB466J.js
|
|
744
868
|
var EventQueue = class {
|
|
745
869
|
constructor(opts) {
|
|
746
870
|
__publicField2(this, "opts", opts);
|
|
@@ -751,17 +875,34 @@ var EventQueue = class {
|
|
|
751
875
|
__publicField2(this, "inflightIds", /* @__PURE__ */ new Set());
|
|
752
876
|
__publicField2(this, "attempt", 0);
|
|
753
877
|
__publicField2(this, "destroyed", false);
|
|
878
|
+
__publicField2(this, "offRecover", null);
|
|
754
879
|
__publicField2(this, "flushIntervalMs");
|
|
755
880
|
__publicField2(this, "maxQueue");
|
|
756
881
|
__publicField2(this, "batchSize");
|
|
757
882
|
__publicField2(this, "maxAttempts");
|
|
758
883
|
this.flushIntervalMs = opts.flushIntervalMs ?? 2e3;
|
|
759
|
-
this.maxQueue = opts.maxQueue ??
|
|
884
|
+
this.maxQueue = opts.maxQueue ?? 500;
|
|
760
885
|
this.batchSize = opts.batchSize ?? 50;
|
|
761
886
|
this.maxAttempts = opts.maxAttempts ?? 5;
|
|
762
887
|
const persisted = readJson(opts.store, opts.storageKey, []);
|
|
763
888
|
if (Array.isArray(persisted)) this.events = persisted.filter((e) => e && typeof e.eventId === "string").slice(-this.maxQueue);
|
|
764
889
|
if (this.events.length) this.schedule();
|
|
890
|
+
this.offRecover = opts.health?.onRecover(() => this.retryNow()) ?? null;
|
|
891
|
+
}
|
|
892
|
+
/** The API is back: drop any pending backoff and send what is queued. */
|
|
893
|
+
retryNow() {
|
|
894
|
+
if (this.destroyed) return;
|
|
895
|
+
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
896
|
+
this.retryTimer = null;
|
|
897
|
+
this.attempt = 0;
|
|
898
|
+
void this.flush();
|
|
899
|
+
}
|
|
900
|
+
retryIn(ms) {
|
|
901
|
+
if (this.retryTimer || this.destroyed) return;
|
|
902
|
+
this.retryTimer = setTimeout(() => {
|
|
903
|
+
this.retryTimer = null;
|
|
904
|
+
void this.flush();
|
|
905
|
+
}, ms);
|
|
765
906
|
}
|
|
766
907
|
get size() {
|
|
767
908
|
return this.events.length;
|
|
@@ -795,6 +936,8 @@ var EventQueue = class {
|
|
|
795
936
|
clearTimeout(this.timer);
|
|
796
937
|
this.timer = null;
|
|
797
938
|
}
|
|
939
|
+
const wait = this.opts.health?.waitMs() ?? 0;
|
|
940
|
+
if (wait > 0) return this.retryIn(wait);
|
|
798
941
|
this.inflight = true;
|
|
799
942
|
const batch = this.events.slice(0, this.batchSize);
|
|
800
943
|
this.inflightIds = new Set(batch.map((e) => e.eventId));
|
|
@@ -818,26 +961,20 @@ var EventQueue = class {
|
|
|
818
961
|
if (this.events.length) this.schedule();
|
|
819
962
|
return;
|
|
820
963
|
}
|
|
821
|
-
this.attempt
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
this.retryTimer = setTimeout(() => {
|
|
827
|
-
this.retryTimer = null;
|
|
828
|
-
void this.flush();
|
|
829
|
-
}, backoffMs(this.attempt - 1));
|
|
964
|
+
this.attempt = Math.min(this.attempt + 1, this.maxAttempts);
|
|
965
|
+
const delay = this.attempt >= this.maxAttempts ? 6e4 : backoffMs(this.attempt - 1);
|
|
966
|
+
this.retryIn(Math.max(delay, this.opts.health?.waitMs() ?? 0));
|
|
830
967
|
}
|
|
831
968
|
/** Best-effort synchronous flush on unload using `sendBeacon`. */
|
|
832
969
|
flushSync() {
|
|
833
970
|
if (this.destroyed || !this.opts.enabled() || !this.opts.beacon) return;
|
|
834
971
|
const batch = this.events.filter((e) => !this.inflightIds.has(e.eventId)).slice(0, this.batchSize);
|
|
835
972
|
if (!batch.length) return;
|
|
836
|
-
if (this.opts.beacon(batch)) {
|
|
973
|
+
if (this.opts.beacon(batch) && (this.opts.health?.reachable ?? true)) {
|
|
837
974
|
const ids = new Set(batch.map((e) => e.eventId));
|
|
838
975
|
this.events = this.events.filter((e) => !ids.has(e.eventId));
|
|
839
|
-
this.persist();
|
|
840
976
|
}
|
|
977
|
+
this.persist();
|
|
841
978
|
}
|
|
842
979
|
clear() {
|
|
843
980
|
this.events = [];
|
|
@@ -845,6 +982,7 @@ var EventQueue = class {
|
|
|
845
982
|
}
|
|
846
983
|
destroy() {
|
|
847
984
|
this.destroyed = true;
|
|
985
|
+
this.offRecover?.();
|
|
848
986
|
if (this.timer) clearTimeout(this.timer);
|
|
849
987
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
850
988
|
this.timer = null;
|
|
@@ -852,6 +990,88 @@ var EventQueue = class {
|
|
|
852
990
|
this.persist();
|
|
853
991
|
}
|
|
854
992
|
};
|
|
993
|
+
var Outbox = class {
|
|
994
|
+
constructor(opts) {
|
|
995
|
+
__publicField2(this, "opts", opts);
|
|
996
|
+
__publicField2(this, "items");
|
|
997
|
+
__publicField2(this, "sending", false);
|
|
998
|
+
__publicField2(this, "attempt", 0);
|
|
999
|
+
__publicField2(this, "timer", null);
|
|
1000
|
+
__publicField2(this, "destroyed", false);
|
|
1001
|
+
__publicField2(this, "offRecover");
|
|
1002
|
+
__publicField2(this, "maxItems");
|
|
1003
|
+
__publicField2(this, "maxAgeMs");
|
|
1004
|
+
this.maxItems = opts.maxItems ?? 50;
|
|
1005
|
+
this.maxAgeMs = opts.maxAgeMs ?? 7 * 24 * 60 * 60 * 1e3;
|
|
1006
|
+
const stored = readJson(opts.store, opts.storageKey, []);
|
|
1007
|
+
const cutoff = Date.now() - this.maxAgeMs;
|
|
1008
|
+
this.items = Array.isArray(stored) ? stored.filter((i) => i && typeof i.path === "string" && typeof i.key === "string" && i.createdAt > cutoff).slice(-this.maxItems) : [];
|
|
1009
|
+
this.offRecover = opts.health?.onRecover(() => {
|
|
1010
|
+
this.attempt = 0;
|
|
1011
|
+
this.retryIn(0);
|
|
1012
|
+
}) ?? null;
|
|
1013
|
+
if (this.items.length) this.retryIn(0);
|
|
1014
|
+
}
|
|
1015
|
+
get size() {
|
|
1016
|
+
return this.items.length;
|
|
1017
|
+
}
|
|
1018
|
+
/** Queues `POST path` and sends it now (or as soon as the API is reachable). */
|
|
1019
|
+
add(key, path, body) {
|
|
1020
|
+
if (this.destroyed) return;
|
|
1021
|
+
this.items = this.items.filter((i) => i.key !== key);
|
|
1022
|
+
this.items.push({ key, path, body, createdAt: Date.now() });
|
|
1023
|
+
if (this.items.length > this.maxItems) this.items.splice(0, this.items.length - this.maxItems);
|
|
1024
|
+
this.persist();
|
|
1025
|
+
void this.flush();
|
|
1026
|
+
}
|
|
1027
|
+
persist() {
|
|
1028
|
+
writeJson(this.opts.store, this.opts.storageKey, this.items);
|
|
1029
|
+
}
|
|
1030
|
+
retryIn(ms) {
|
|
1031
|
+
if (this.destroyed) return;
|
|
1032
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1033
|
+
this.timer = setTimeout(() => {
|
|
1034
|
+
this.timer = null;
|
|
1035
|
+
void this.flush();
|
|
1036
|
+
}, ms);
|
|
1037
|
+
}
|
|
1038
|
+
/** Sends queued requests in order, one at a time. */
|
|
1039
|
+
async flush() {
|
|
1040
|
+
if (this.destroyed || this.sending || !this.items.length) return;
|
|
1041
|
+
const wait = this.opts.health?.waitMs() ?? 0;
|
|
1042
|
+
if (wait > 0) return this.retryIn(wait);
|
|
1043
|
+
this.sending = true;
|
|
1044
|
+
try {
|
|
1045
|
+
while (this.items.length && !this.destroyed) {
|
|
1046
|
+
const item = this.items[0];
|
|
1047
|
+
let retry = false;
|
|
1048
|
+
try {
|
|
1049
|
+
const res = await this.opts.api.post(item.path, item.body);
|
|
1050
|
+
retry = !res.ok && (res.status === 429 || res.status >= 500);
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
retry = true;
|
|
1053
|
+
this.opts.onError?.(err);
|
|
1054
|
+
}
|
|
1055
|
+
if (retry) {
|
|
1056
|
+
this.attempt += 1;
|
|
1057
|
+
this.retryIn(Math.max(backoffMs(this.attempt - 1, 1e3, 6e4), this.opts.health?.waitMs() ?? 0));
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
this.attempt = 0;
|
|
1061
|
+
this.items = this.items.filter((i) => i !== item);
|
|
1062
|
+
this.persist();
|
|
1063
|
+
}
|
|
1064
|
+
} finally {
|
|
1065
|
+
this.sending = false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
destroy() {
|
|
1069
|
+
this.destroyed = true;
|
|
1070
|
+
this.offRecover?.();
|
|
1071
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1072
|
+
this.timer = null;
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
855
1075
|
var Emitter = class {
|
|
856
1076
|
constructor(onError) {
|
|
857
1077
|
__publicField2(this, "onError", onError);
|
|
@@ -1407,12 +1627,29 @@ function createSseParser(onEvent) {
|
|
|
1407
1627
|
}
|
|
1408
1628
|
};
|
|
1409
1629
|
}
|
|
1630
|
+
var STREAM_RESPONSE_TIMEOUT_MS2 = 3e4;
|
|
1631
|
+
var STREAM_IDLE_TIMEOUT_MS2 = 6e4;
|
|
1410
1632
|
function xhrStreamPost() {
|
|
1411
1633
|
if (typeof XMLHttpRequest === "undefined") return null;
|
|
1412
1634
|
return (url, headers, body, onChunk) => new Promise((resolve, reject) => {
|
|
1413
1635
|
const xhr = new XMLHttpRequest();
|
|
1414
1636
|
let seen = 0;
|
|
1415
1637
|
let streamed = false;
|
|
1638
|
+
let timer = null;
|
|
1639
|
+
let settled = false;
|
|
1640
|
+
const settle = (fn) => {
|
|
1641
|
+
if (settled) return;
|
|
1642
|
+
settled = true;
|
|
1643
|
+
if (timer) clearTimeout(timer);
|
|
1644
|
+
fn();
|
|
1645
|
+
};
|
|
1646
|
+
const arm = (ms, what) => {
|
|
1647
|
+
if (timer) clearTimeout(timer);
|
|
1648
|
+
timer = setTimeout(() => {
|
|
1649
|
+
settle(() => reject(new Error(`Scout ${what} timed out`)));
|
|
1650
|
+
xhr.abort();
|
|
1651
|
+
}, ms);
|
|
1652
|
+
};
|
|
1416
1653
|
const pump = () => {
|
|
1417
1654
|
const type = xhr.getResponseHeader("content-type") || "";
|
|
1418
1655
|
if (!type.includes("text/event-stream")) return;
|
|
@@ -1425,8 +1662,15 @@ function xhrStreamPost() {
|
|
|
1425
1662
|
};
|
|
1426
1663
|
xhr.open("POST", url);
|
|
1427
1664
|
for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);
|
|
1428
|
-
xhr.
|
|
1429
|
-
|
|
1665
|
+
xhr.onreadystatechange = () => {
|
|
1666
|
+
if (!settled && xhr.readyState >= 2 && xhr.readyState < 4) arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1667
|
+
};
|
|
1668
|
+
xhr.onprogress = () => {
|
|
1669
|
+
if (settled) return;
|
|
1670
|
+
arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1671
|
+
pump();
|
|
1672
|
+
};
|
|
1673
|
+
xhr.onload = () => settle(() => {
|
|
1430
1674
|
pump();
|
|
1431
1675
|
let json = null;
|
|
1432
1676
|
if (!streamed) {
|
|
@@ -1437,27 +1681,74 @@ function xhrStreamPost() {
|
|
|
1437
1681
|
}
|
|
1438
1682
|
}
|
|
1439
1683
|
resolve({ status: xhr.status, json, streamed });
|
|
1440
|
-
};
|
|
1441
|
-
xhr.onerror = () => reject(new Error("Network request failed"));
|
|
1684
|
+
});
|
|
1685
|
+
xhr.onerror = () => settle(() => reject(new Error("Network request failed")));
|
|
1686
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS2, "request");
|
|
1442
1687
|
xhr.send(JSON.stringify(body));
|
|
1443
1688
|
});
|
|
1444
1689
|
}
|
|
1690
|
+
function bodyReader(res) {
|
|
1691
|
+
const body = res.body;
|
|
1692
|
+
const Decoder = globalThis.TextDecoder;
|
|
1693
|
+
return body && typeof body.getReader === "function" && Decoder ? { reader: body.getReader(), decoder: new Decoder() } : null;
|
|
1694
|
+
}
|
|
1445
1695
|
function fetchStreamPost(fetchImpl) {
|
|
1446
1696
|
return async (url, headers, body, onChunk) => {
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1697
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
1698
|
+
let timer = null;
|
|
1699
|
+
let timedOut = "";
|
|
1700
|
+
let fail = () => {
|
|
1701
|
+
};
|
|
1702
|
+
const expired = new Promise((_, reject) => fail = reject);
|
|
1703
|
+
expired.catch(() => {
|
|
1704
|
+
});
|
|
1705
|
+
const arm = (ms, what) => {
|
|
1706
|
+
if (timer) clearTimeout(timer);
|
|
1707
|
+
timer = setTimeout(() => {
|
|
1708
|
+
timedOut = what;
|
|
1709
|
+
ac?.abort();
|
|
1710
|
+
fail(new Error(`Scout ${what} timed out`));
|
|
1711
|
+
}, ms);
|
|
1712
|
+
};
|
|
1713
|
+
const race = (p) => Promise.race([p, expired]);
|
|
1714
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS2, "request");
|
|
1455
1715
|
try {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1716
|
+
const res = await race(fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body), signal: ac?.signal }));
|
|
1717
|
+
arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1718
|
+
const type = res.headers.get("content-type") || "";
|
|
1719
|
+
const sse = type.includes("text/event-stream");
|
|
1720
|
+
const streaming = sse ? bodyReader(res) : null;
|
|
1721
|
+
if (streaming) {
|
|
1722
|
+
const { reader, decoder } = streaming;
|
|
1723
|
+
for (; ; ) {
|
|
1724
|
+
const { done, value } = await race(reader.read());
|
|
1725
|
+
if (value) {
|
|
1726
|
+
arm(STREAM_IDLE_TIMEOUT_MS2, "reply");
|
|
1727
|
+
onChunk(decoder.decode(value, { stream: true }));
|
|
1728
|
+
}
|
|
1729
|
+
if (done) break;
|
|
1730
|
+
}
|
|
1731
|
+
const rest = decoder.decode();
|
|
1732
|
+
if (rest) onChunk(rest);
|
|
1733
|
+
return { status: res.status, json: null, streamed: true };
|
|
1734
|
+
}
|
|
1735
|
+
const text = await race(res.text());
|
|
1736
|
+
if (sse) {
|
|
1737
|
+
onChunk(text);
|
|
1738
|
+
return { status: res.status, json: null, streamed: true };
|
|
1739
|
+
}
|
|
1740
|
+
let json = null;
|
|
1741
|
+
try {
|
|
1742
|
+
json = JSON.parse(text || "null");
|
|
1743
|
+
} catch {
|
|
1744
|
+
json = null;
|
|
1745
|
+
}
|
|
1746
|
+
return { status: res.status, json, streamed: false };
|
|
1747
|
+
} catch (err) {
|
|
1748
|
+
throw timedOut ? new Error(`Scout ${timedOut} timed out`) : err;
|
|
1749
|
+
} finally {
|
|
1750
|
+
if (timer) clearTimeout(timer);
|
|
1459
1751
|
}
|
|
1460
|
-
return { status: res.status, json, streamed: false };
|
|
1461
1752
|
};
|
|
1462
1753
|
}
|
|
1463
1754
|
var byNewest = (a, b) => Date.parse(b.lastMessageAt) - Date.parse(a.lastMessageAt);
|
|
@@ -1642,7 +1933,15 @@ var Messenger = class {
|
|
|
1642
1933
|
}
|
|
1643
1934
|
});
|
|
1644
1935
|
try {
|
|
1645
|
-
|
|
1936
|
+
let res;
|
|
1937
|
+
try {
|
|
1938
|
+
res = await this.deps.streamPost(this.url(path), headers, payload, (chunk) => parser.feed(chunk));
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
this.deps.health?.failure();
|
|
1941
|
+
throw err;
|
|
1942
|
+
}
|
|
1943
|
+
if (isServerFailure(res.status)) this.deps.health?.failure();
|
|
1944
|
+
else this.deps.health?.success();
|
|
1646
1945
|
parser.end();
|
|
1647
1946
|
if (res.status < 200 || res.status >= 300) throw new Error(`Request failed (${res.status})`);
|
|
1648
1947
|
if (!res.streamed && res.json) {
|
|
@@ -1829,9 +2128,20 @@ var PersistentStore = class {
|
|
|
1829
2128
|
|
|
1830
2129
|
// src/core/client.ts
|
|
1831
2130
|
var SDK_NAME = "@guidelytech/react-native";
|
|
1832
|
-
var SDK_VERSION = "0.
|
|
2131
|
+
var SDK_VERSION = "0.10.0";
|
|
1833
2132
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
1834
2133
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2134
|
+
var CACHED_START_MS = 1500;
|
|
2135
|
+
function settlesWithin(promise, ms) {
|
|
2136
|
+
return new Promise((resolve) => {
|
|
2137
|
+
const timer = setTimeout(() => resolve(false), ms);
|
|
2138
|
+
const done = () => {
|
|
2139
|
+
clearTimeout(timer);
|
|
2140
|
+
resolve(true);
|
|
2141
|
+
};
|
|
2142
|
+
promise.then(done, done);
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
1835
2145
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1836
2146
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
1837
2147
|
var newSession = () => ({ id: `s_${randomId(16)}`, startedAt: nowIso(), lastActiveAt: nowIso(), shownGuides: [], events: [] });
|
|
@@ -1844,6 +2154,8 @@ var GuidelyClient = class {
|
|
|
1844
2154
|
__publicField(this, "store");
|
|
1845
2155
|
__publicField(this, "emitter");
|
|
1846
2156
|
__publicField(this, "queue", null);
|
|
2157
|
+
/** Survey answers, guide completions/dismissals and checklist progress, kept until the server has them. */
|
|
2158
|
+
__publicField(this, "outbox", null);
|
|
1847
2159
|
__publicField(this, "config", null);
|
|
1848
2160
|
__publicField(this, "configFetchedAt", 0);
|
|
1849
2161
|
__publicField(this, "etag", null);
|
|
@@ -1906,7 +2218,7 @@ var GuidelyClient = class {
|
|
|
1906
2218
|
this.emitter = new Emitter((err) => this.reportError(err));
|
|
1907
2219
|
this.api = new Api(this.options.apiUrl, this.options.projectId, options.fetch);
|
|
1908
2220
|
this.store = new PersistentStore(options.storage ?? null, (err) => this.log("storage error", err));
|
|
1909
|
-
if (!options.storage) this.log("no `storage` passed: identity
|
|
2221
|
+
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.");
|
|
1910
2222
|
this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
|
|
1911
2223
|
if (options.user) this.user = { ...options.user };
|
|
1912
2224
|
this.flags = this.createFlags(options.flags);
|
|
@@ -1957,7 +2269,7 @@ var GuidelyClient = class {
|
|
|
1957
2269
|
async bootstrap() {
|
|
1958
2270
|
const key = this.options.projectId;
|
|
1959
2271
|
try {
|
|
1960
|
-
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)]);
|
|
2272
|
+
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)]);
|
|
1961
2273
|
if (this.destroyed) return;
|
|
1962
2274
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
1963
2275
|
if (!this.anonymousId) {
|
|
@@ -1977,22 +2289,27 @@ var GuidelyClient = class {
|
|
|
1977
2289
|
store: this.store,
|
|
1978
2290
|
storageKey: KEYS.queue(key),
|
|
1979
2291
|
enabled: () => this.analyticsEnabled(),
|
|
2292
|
+
health: this.api.health,
|
|
1980
2293
|
send: async (events) => {
|
|
1981
2294
|
const res = await this.api.post("/events", { events, sentAt: nowIso(), sdk: { name: SDK_NAME, version: SDK_VERSION } });
|
|
1982
2295
|
return { ok: res.ok, retry: !(res.status >= 400 && res.status < 500 && res.status !== 429) };
|
|
1983
2296
|
},
|
|
1984
2297
|
onError: (err) => this.reportError(err)
|
|
1985
2298
|
});
|
|
2299
|
+
this.outbox = new Outbox({ api: this.api, store: this.store, storageKey: KEYS.outbox(key), health: this.api.health });
|
|
1986
2300
|
this.hydrated = true;
|
|
1987
2301
|
const cached = readJson(this.store, KEYS.config(key), null);
|
|
1988
2302
|
this.etag = this.store.get(KEYS.etag(key));
|
|
1989
2303
|
if (cached && Array.isArray(cached.guides)) this.config = forPlatform(cached, this.bridge.device.platform);
|
|
1990
2304
|
this.syncMessenger();
|
|
1991
2305
|
this.pending.splice(0).forEach((fn) => fn());
|
|
1992
|
-
|
|
2306
|
+
const before = this.config?.configVersion;
|
|
2307
|
+
const fresh = this.fetchConfig();
|
|
2308
|
+
if (!this.config) await fresh;
|
|
2309
|
+
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
1993
2310
|
if (this.destroyed) return;
|
|
1994
2311
|
this.wireTimers();
|
|
1995
|
-
if (this.user?.id) await this.identify(this.user.id, this.user.traits, this.user.account);
|
|
2312
|
+
if (this.user?.id) await settlesWithin(this.identify(this.user.id, this.user.traits, this.user.account), CACHED_START_MS);
|
|
1996
2313
|
if (this.destroyed) return;
|
|
1997
2314
|
this.isReady = true;
|
|
1998
2315
|
if (this.config) this.emitter.emit("ready", this.config);
|
|
@@ -2032,7 +2349,9 @@ var GuidelyClient = class {
|
|
|
2032
2349
|
}
|
|
2033
2350
|
}
|
|
2034
2351
|
wireTimers() {
|
|
2035
|
-
const refresh = setInterval(() =>
|
|
2352
|
+
const refresh = setInterval(() => {
|
|
2353
|
+
if (this.api.health.canSend()) void this.refresh();
|
|
2354
|
+
}, this.options.configRefreshMs);
|
|
2036
2355
|
this.cleanups.push(() => clearInterval(refresh));
|
|
2037
2356
|
const poll = setInterval(() => {
|
|
2038
2357
|
if (!this.isReady || !this.config || this.backgroundedAt !== null) return;
|
|
@@ -2048,6 +2367,11 @@ var GuidelyClient = class {
|
|
|
2048
2367
|
if (this.destroyed) return;
|
|
2049
2368
|
const before = this.config?.configVersion;
|
|
2050
2369
|
await this.fetchConfig();
|
|
2370
|
+
this.applyConfigChange(before);
|
|
2371
|
+
}
|
|
2372
|
+
/** Re-renders after a config change (refresh, or a config that arrived after start). */
|
|
2373
|
+
applyConfigChange(before) {
|
|
2374
|
+
if (this.destroyed || !this.isReady) return;
|
|
2051
2375
|
if (this.config && this.config.configVersion !== before) {
|
|
2052
2376
|
this.notify();
|
|
2053
2377
|
this.evaluate("ready");
|
|
@@ -2061,6 +2385,7 @@ var GuidelyClient = class {
|
|
|
2061
2385
|
this.session.lastActiveAt = nowIso();
|
|
2062
2386
|
this.persistSession();
|
|
2063
2387
|
void this.queue?.flush();
|
|
2388
|
+
void this.outbox?.flush();
|
|
2064
2389
|
void this.store.flush();
|
|
2065
2390
|
void this.flags.flush();
|
|
2066
2391
|
this.messenger?.setForeground(false);
|
|
@@ -2074,7 +2399,7 @@ var GuidelyClient = class {
|
|
|
2074
2399
|
this.firedDelays.clear();
|
|
2075
2400
|
}
|
|
2076
2401
|
void this.flags.refresh();
|
|
2077
|
-
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs) void this.refresh();
|
|
2402
|
+
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
2078
2403
|
else if (this.isReady) this.evaluate("ready");
|
|
2079
2404
|
}
|
|
2080
2405
|
}
|
|
@@ -2100,6 +2425,7 @@ var GuidelyClient = class {
|
|
|
2100
2425
|
this.cleanups.splice(0).forEach((fn) => fn());
|
|
2101
2426
|
void this.queue?.flush();
|
|
2102
2427
|
this.queue?.destroy();
|
|
2428
|
+
this.outbox?.destroy();
|
|
2103
2429
|
this.flags.destroy();
|
|
2104
2430
|
this.emitter.clear();
|
|
2105
2431
|
this.uiListeners.clear();
|
|
@@ -2513,7 +2839,13 @@ var GuidelyClient = class {
|
|
|
2513
2839
|
if (reason === "completed") this.completeChecklistTasksBy("guide_completed", guide);
|
|
2514
2840
|
}
|
|
2515
2841
|
syncGuideState(guideId, update) {
|
|
2516
|
-
|
|
2842
|
+
const body = { anonymousId: this.anonymousId, userId: this.user?.id || null, updates: [{ guideId, ...update }] };
|
|
2843
|
+
if (update.impression) void postWithRetry(this.api, "/guide-state", body);
|
|
2844
|
+
else this.sendDurably(`guide:${guideId}:${update.completed ? "completed" : "dismissed"}:${this.anonymousId}`, "/guide-state", body);
|
|
2845
|
+
}
|
|
2846
|
+
/** Queues a write in the persisted outbox (sent now, or once the API is reachable again). */
|
|
2847
|
+
sendDurably(key, path, body) {
|
|
2848
|
+
this.whenHydrated(() => this.outbox?.add(key, path, body));
|
|
2517
2849
|
}
|
|
2518
2850
|
show(guide, manual, stepId) {
|
|
2519
2851
|
try {
|
|
@@ -2721,7 +3053,7 @@ var GuidelyClient = class {
|
|
|
2721
3053
|
progress.dismissedAt = nowIso();
|
|
2722
3054
|
this.persistState();
|
|
2723
3055
|
this.enqueue("checklist_dismissed", { guideId: guide.id, guideVersion: guide.version });
|
|
2724
|
-
|
|
3056
|
+
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 });
|
|
2725
3057
|
this.checklists.delete(guide.id);
|
|
2726
3058
|
this.notify();
|
|
2727
3059
|
}
|
|
@@ -2734,7 +3066,7 @@ var GuidelyClient = class {
|
|
|
2734
3066
|
this.persistState();
|
|
2735
3067
|
this.recordEnd(guide, "completed");
|
|
2736
3068
|
this.enqueue("survey_submitted", { guideId: guide.id, guideVersion: guide.version, properties: { responseId } });
|
|
2737
|
-
|
|
3069
|
+
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 });
|
|
2738
3070
|
this.emitter.emit("survey:submit", { guide, answers, responseId });
|
|
2739
3071
|
this.emitter.emit("guide:complete", guide);
|
|
2740
3072
|
this.surveyView = { ...this.surveyView, submitted: true };
|
|
@@ -2791,7 +3123,7 @@ var GuidelyClient = class {
|
|
|
2791
3123
|
this.enqueue("checklist_completed", { guideId: guide.id, guideVersion: guide.version });
|
|
2792
3124
|
}
|
|
2793
3125
|
this.persistState();
|
|
2794
|
-
|
|
3126
|
+
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 });
|
|
2795
3127
|
this.emitter.emit("checklist:task", { guide, task, progress: this.getChecklistProgress(guide.id) });
|
|
2796
3128
|
if (complete) {
|
|
2797
3129
|
this.emitter.emit("checklist:complete", guide);
|
|
@@ -2928,7 +3260,8 @@ var GuidelyClient = class {
|
|
|
2928
3260
|
},
|
|
2929
3261
|
onMessage: (message, conversation) => this.emitter.emit("conversation:message", { message, conversation }),
|
|
2930
3262
|
onStartGuide: (key) => this.setTimer(() => this.startGuide(key), 0),
|
|
2931
|
-
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err)
|
|
3263
|
+
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err),
|
|
3264
|
+
health: this.api.health
|
|
2932
3265
|
});
|
|
2933
3266
|
this.resourceCenterOpen = false;
|
|
2934
3267
|
}
|
|
@@ -3033,6 +3366,7 @@ var GuidelyClient = class {
|
|
|
3033
3366
|
audienceIds: this.state.audienceIds,
|
|
3034
3367
|
session: this.session,
|
|
3035
3368
|
pendingEvents: this.queue?.size ?? 0,
|
|
3369
|
+
pendingWrites: this.outbox?.size ?? 0,
|
|
3036
3370
|
targets: this.targets.ids(),
|
|
3037
3371
|
flagsReady: this.flags.isReady()
|
|
3038
3372
|
};
|