@usergist/sdk-core 0.1.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/dist/mobile.js ADDED
@@ -0,0 +1,710 @@
1
+ // src/types/segment-dsl.ts
2
+ function emptySegmentDsl() {
3
+ return { version: 1, root: { combinator: "AND", predicates: [] } };
4
+ }
5
+
6
+ // src/types/api.ts
7
+ function ok(data, meta) {
8
+ return meta ? { success: true, data, meta } : { success: true, data };
9
+ }
10
+ function err(code, message, details) {
11
+ return {
12
+ success: false,
13
+ error: details === void 0 ? { code, message } : { code, message, details }
14
+ };
15
+ }
16
+
17
+ // src/types/survey.ts
18
+ var SURVEY_END_SENTINEL = "__end__";
19
+
20
+ // src/types/inapp-message.ts
21
+ var INAPP_SHOWN_EVENT_NAME = "$inapp_shown";
22
+ var INAPP_DISMISSED_EVENT_NAME = "$inapp_dismissed";
23
+ var INAPP_AUTO_DISMISSED_EVENT_NAME = "$inapp_auto_dismissed";
24
+ var INAPP_CTA_CLICKED_EVENT_NAME = "$inapp_cta_clicked";
25
+
26
+ // src/types/request.ts
27
+ var REQUEST_SUBMITTED_EVENT_NAME = "$request_submitted";
28
+ var REQUEST_UPVOTED_EVENT_NAME = "$request_upvoted";
29
+ var REQUEST_UNUPVOTED_EVENT_NAME = "$request_unupvoted";
30
+ var REQUEST_FOLLOWED_EVENT_NAME = "$request_followed";
31
+ var REQUEST_UNFOLLOWED_EVENT_NAME = "$request_unfollowed";
32
+ var REQUEST_STATUS_CHANGED_EVENT_NAME = "$request_status_changed";
33
+ var REQUEST_RESPONDED_EVENT_NAME = "$request_responded";
34
+ var REQUEST_COMMENTED_EVENT_NAME = "$request_commented";
35
+ var REQUEST_COMMENT_EDITED_EVENT_NAME = "$request_comment_edited";
36
+ var REQUEST_COMMENT_DELETED_EVENT_NAME = "$request_comment_deleted";
37
+
38
+ // src/types/targeting.ts
39
+ function emptyAudienceSpec() {
40
+ return { version: 1, mode: "everyone", combinator: "all", conditions: [] };
41
+ }
42
+ var APP_OPEN_EVENT_NAME = "$app_open";
43
+ var AUDIENCE_JOIN_EVENT_NAME = "$audience_join";
44
+ var AUDIENCE_JOIN_TRIGGER_KIND = "audience_join";
45
+ var APP_VERSION_CHANGED_EVENT_NAME = "$app_version_changed";
46
+ function defaultTriggerSpec() {
47
+ return { kind: "app_open" };
48
+ }
49
+ function defaultEventTrigger(eventName = "") {
50
+ return {
51
+ kind: "event",
52
+ eventName,
53
+ occurrence: { mode: "every" }
54
+ };
55
+ }
56
+
57
+ // src/types/themes.ts
58
+ var RADIUS_PRESETS = {
59
+ sharp: 4,
60
+ soft: 12,
61
+ rounded: 20,
62
+ pill: 999
63
+ };
64
+ var JAKARTA = "Plus Jakarta Sans";
65
+ var THEME_PRESETS = [
66
+ {
67
+ id: "minimal",
68
+ name: "Minimal",
69
+ description: "Clean neutral palette",
70
+ colors: {
71
+ primary: "#111827",
72
+ background: "#FFFFFF",
73
+ text: "#111827",
74
+ subtext: "#6B7280",
75
+ border: "#E5E7EB",
76
+ button: "#111827"
77
+ },
78
+ radius: 16,
79
+ fontFamily: JAKARTA,
80
+ swatches: ["#111827", "#FFFFFF", "#E5E7EB"]
81
+ },
82
+ {
83
+ id: "ocean",
84
+ name: "Ocean",
85
+ description: "Cool blue tones",
86
+ colors: {
87
+ primary: "#0EA5E9",
88
+ background: "#F0F9FF",
89
+ text: "#0C4A6E",
90
+ subtext: "#0369A1",
91
+ border: "#BAE6FD",
92
+ button: "#0EA5E9"
93
+ },
94
+ radius: 16,
95
+ fontFamily: JAKARTA,
96
+ swatches: ["#0EA5E9", "#0C4A6E", "#BAE6FD"]
97
+ },
98
+ {
99
+ id: "sunset",
100
+ name: "Sunset",
101
+ description: "Warm orange glow",
102
+ colors: {
103
+ primary: "#F97316",
104
+ background: "#FFF7ED",
105
+ text: "#7C2D12",
106
+ subtext: "#C2410C",
107
+ border: "#FED7AA",
108
+ button: "#F97316"
109
+ },
110
+ radius: 16,
111
+ fontFamily: JAKARTA,
112
+ swatches: ["#F97316", "#7C2D12", "#FED7AA"]
113
+ },
114
+ {
115
+ id: "forest",
116
+ name: "Forest",
117
+ description: "Natural green hues",
118
+ colors: {
119
+ primary: "#16A34A",
120
+ background: "#F0FDF4",
121
+ text: "#14532D",
122
+ subtext: "#166534",
123
+ border: "#BBF7D0",
124
+ button: "#16A34A"
125
+ },
126
+ radius: 16,
127
+ fontFamily: JAKARTA,
128
+ swatches: ["#16A34A", "#14532D", "#BBF7D0"]
129
+ },
130
+ {
131
+ id: "midnight",
132
+ name: "Midnight",
133
+ description: "Deep dark mode",
134
+ colors: {
135
+ primary: "#A78BFA",
136
+ background: "#0F172A",
137
+ text: "#F8FAFC",
138
+ subtext: "#94A3B8",
139
+ border: "#1E293B",
140
+ button: "#A78BFA"
141
+ },
142
+ radius: 16,
143
+ fontFamily: JAKARTA,
144
+ swatches: ["#A78BFA", "#0F172A", "#1E293B"]
145
+ },
146
+ {
147
+ id: "candy",
148
+ name: "Candy",
149
+ description: "Playful pink + violet",
150
+ colors: {
151
+ primary: "#EC4899",
152
+ background: "#FDF2F8",
153
+ text: "#831843",
154
+ subtext: "#BE185D",
155
+ border: "#FBCFE8",
156
+ button: "#EC4899"
157
+ },
158
+ radius: 20,
159
+ fontFamily: JAKARTA,
160
+ swatches: ["#EC4899", "#831843", "#FBCFE8"]
161
+ },
162
+ {
163
+ id: "mono",
164
+ name: "Mono",
165
+ description: "High-contrast B&W",
166
+ colors: {
167
+ primary: "#000000",
168
+ background: "#FFFFFF",
169
+ text: "#000000",
170
+ subtext: "#525252",
171
+ border: "#000000",
172
+ button: "#000000"
173
+ },
174
+ radius: 4,
175
+ fontFamily: JAKARTA,
176
+ swatches: ["#000000", "#FFFFFF", "#525252"]
177
+ },
178
+ {
179
+ id: "coral",
180
+ name: "Coral",
181
+ description: "Soft coral accent",
182
+ colors: {
183
+ primary: "#FB7185",
184
+ background: "#FFF1F2",
185
+ text: "#881337",
186
+ subtext: "#9F1239",
187
+ border: "#FECDD3",
188
+ button: "#FB7185"
189
+ },
190
+ radius: 16,
191
+ fontFamily: JAKARTA,
192
+ swatches: ["#FB7185", "#881337", "#FECDD3"]
193
+ },
194
+ {
195
+ id: "ink",
196
+ name: "Ink",
197
+ description: "Deep navy editorial",
198
+ colors: {
199
+ primary: "#1E3A8A",
200
+ background: "#F8FAFC",
201
+ text: "#0F172A",
202
+ subtext: "#475569",
203
+ border: "#CBD5E1",
204
+ button: "#1E3A8A"
205
+ },
206
+ radius: 12,
207
+ fontFamily: JAKARTA,
208
+ swatches: ["#1E3A8A", "#0F172A", "#CBD5E1"]
209
+ },
210
+ {
211
+ id: "mint",
212
+ name: "Mint",
213
+ description: "Fresh teal vibe",
214
+ colors: {
215
+ primary: "#14B8A6",
216
+ background: "#F0FDFA",
217
+ text: "#134E4A",
218
+ subtext: "#0F766E",
219
+ border: "#99F6E4",
220
+ button: "#14B8A6"
221
+ },
222
+ radius: 16,
223
+ fontFamily: JAKARTA,
224
+ swatches: ["#14B8A6", "#134E4A", "#99F6E4"]
225
+ },
226
+ {
227
+ id: "slate",
228
+ name: "Slate",
229
+ description: "Quiet professional",
230
+ colors: {
231
+ primary: "#475569",
232
+ background: "#F1F5F9",
233
+ text: "#0F172A",
234
+ subtext: "#64748B",
235
+ border: "#CBD5E1",
236
+ button: "#475569"
237
+ },
238
+ radius: 12,
239
+ fontFamily: JAKARTA,
240
+ swatches: ["#475569", "#0F172A", "#CBD5E1"]
241
+ },
242
+ {
243
+ id: "aurora",
244
+ name: "Aurora",
245
+ description: "Vibrant gradient pop",
246
+ colors: {
247
+ primary: "#8B5CF6",
248
+ background: "#FAF5FF",
249
+ text: "#3B0764",
250
+ subtext: "#7E22CE",
251
+ border: "#E9D5FF",
252
+ accent: "#EC4899",
253
+ button: "#8B5CF6"
254
+ },
255
+ radius: 20,
256
+ fontFamily: JAKARTA,
257
+ swatches: ["#8B5CF6", "#EC4899", "#E9D5FF"]
258
+ }
259
+ ];
260
+ function getThemePresetById(id) {
261
+ return THEME_PRESETS.find((preset) => preset.id === id);
262
+ }
263
+ function defaultThemePreset() {
264
+ return THEME_PRESETS[0];
265
+ }
266
+
267
+ // src/types/brand.ts
268
+ function brandTokensFromPreset(preset) {
269
+ return {
270
+ colors: {
271
+ primary: preset.colors.primary,
272
+ background: preset.colors.background,
273
+ text: preset.colors.text,
274
+ subtext: preset.colors.subtext,
275
+ border: preset.colors.border
276
+ },
277
+ radius: preset.radius,
278
+ fontFamily: preset.fontFamily
279
+ };
280
+ }
281
+ function promptThemeFromBrandTokens(tokens) {
282
+ return {
283
+ colors: { ...tokens.colors },
284
+ radius: tokens.radius,
285
+ fontFamily: tokens.fontFamily
286
+ };
287
+ }
288
+ function inAppColorsFromBrandTokens(tokens) {
289
+ return {
290
+ backgroundColor: tokens.colors.background,
291
+ accentColor: tokens.colors.primary
292
+ };
293
+ }
294
+ function requestAccentFromBrandTokens(tokens) {
295
+ return tokens.colors.primary;
296
+ }
297
+
298
+ // src/types/integration.ts
299
+ var INTEGRATION_PROVIDERS = ["amplitude", "mixpanel"];
300
+ var INTEGRATION_CATEGORIES = [
301
+ "feedback",
302
+ "surveys",
303
+ "inapp",
304
+ "push",
305
+ "requests",
306
+ "lifecycle"
307
+ ];
308
+ var USER_IDENTIFIED_EVENT_NAME = "$user_identified";
309
+ function camelize(value) {
310
+ return value.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
311
+ }
312
+ var ids = (...keys) => Object.fromEntries(keys.map((key) => [key, [key, camelize(key)]]));
313
+ var pushEvents = [
314
+ ["$push_sent", "UserGist Push Sent"],
315
+ ["$push_delivered", "UserGist Push Delivered"],
316
+ ["$push_received", "UserGist Push Received"],
317
+ ["$push_displayed", "UserGist Push Displayed"],
318
+ ["$push_opened", "UserGist Push Opened"],
319
+ ["$push_dismissed", "UserGist Push Dismissed"],
320
+ ["$push_clicked", "UserGist Push Clicked"],
321
+ ["$push_action_clicked", "UserGist Push Action Clicked"],
322
+ ["$push_bounced", "UserGist Push Bounced"]
323
+ ];
324
+ var requestEvents = [
325
+ ["$request_submitted", "UserGist Request Submitted", "actor"],
326
+ ["$request_upvoted", "UserGist Request Upvoted", "actor"],
327
+ ["$request_unupvoted", "UserGist Request Unupvoted", "actor"],
328
+ ["$request_followed", "UserGist Request Followed", "actor"],
329
+ ["$request_unfollowed", "UserGist Request Unfollowed", "actor"],
330
+ ["$request_status_changed", "UserGist Request Status Changed", "affected_user"],
331
+ ["$request_responded", "UserGist Request Responded", "affected_user"],
332
+ ["$request_commented", "UserGist Request Commented", "actor"],
333
+ ["$request_comment_edited", "UserGist Request Comment Edited", "actor"],
334
+ ["$request_comment_deleted", "UserGist Request Comment Deleted", "affected_user"]
335
+ ];
336
+ var answerProperties = {
337
+ ...ids(
338
+ "question_id",
339
+ "question_type",
340
+ "question_title",
341
+ "answer_format",
342
+ "answer_present",
343
+ "answer_numeric_value",
344
+ "answer_selection_count",
345
+ "answer_text_length"
346
+ ),
347
+ // These aliases are generated only by the trusted server response paths.
348
+ // The API delivery boundary removes them from public SDK events before this
349
+ // normalizer runs. The complete answer_value stays in UserGist; destinations
350
+ // receive a value only for server-verified structured data.
351
+ answer_value: ["__usergist_destination_answer_value"],
352
+ answer_display_value: ["__usergist_destination_answer_display_value"]
353
+ };
354
+ var CORE_INTEGRATION_EVENTS = [
355
+ { key: "$app_open", name: "UserGist App Opened", category: "lifecycle", subjectRole: "actor", properties: {} },
356
+ { key: "$app_version_changed", name: "UserGist App Version Changed", category: "lifecycle", subjectRole: "actor", properties: { ...ids("old_version", "new_version") } },
357
+ { key: USER_IDENTIFIED_EVENT_NAME, name: "UserGist User Identified", category: "identity", subjectRole: "actor", required: true, properties: {} },
358
+ { key: "$feedback_prompt_shown", name: "UserGist Feedback Prompt Shown", category: "feedback", subjectRole: "actor", properties: { ...ids("prompt_id") } },
359
+ { key: "$feedback_response", name: "UserGist Feedback Response Submitted", category: "feedback", subjectRole: "actor", properties: { ...ids("response_id", "prompt_id", "prompt_name", "dismissed", "latency_ms", "answer_count", "feedback_types") } },
360
+ { key: "$feedback_answer_submitted", name: "UserGist Feedback Answer Submitted", category: "feedback", subjectRole: "actor", properties: { ...ids("response_id", "prompt_id", "prompt_name"), ...answerProperties } },
361
+ { key: "$survey_started", name: "UserGist Survey Started", category: "surveys", subjectRole: "actor", properties: { ...ids("campaign_id", "survey_name", "attempt_id", "language", "source") } },
362
+ { key: "$survey_answer_submitted", name: "UserGist Survey Answer Submitted", category: "surveys", subjectRole: "actor", properties: { ...ids("response_id", "campaign_id", "survey_name", "attempt_id", "language"), ...answerProperties } },
363
+ { key: "$survey_completed", name: "UserGist Survey Completed", category: "surveys", subjectRole: "actor", properties: { ...ids("campaign_id", "survey_name", "attempt_id", "language", "source", "duration_ms") } },
364
+ { key: "$survey_abandoned", name: "UserGist Survey Abandoned", category: "surveys", subjectRole: "actor", properties: { ...ids("campaign_id", "survey_name", "attempt_id", "language", "source", "duration_ms") } },
365
+ { key: "$inapp_shown", name: "UserGist In-App Message Shown", category: "inapp", subjectRole: "actor", properties: { ...ids("message_id") } },
366
+ { key: "$inapp_dismissed", name: "UserGist In-App Message Dismissed", category: "inapp", subjectRole: "actor", properties: { ...ids("message_id", "dismiss_reason") } },
367
+ { key: "$inapp_auto_dismissed", name: "UserGist In-App Message Auto-Dismissed", category: "inapp", subjectRole: "actor", properties: { ...ids("message_id", "dismiss_reason") } },
368
+ { key: "$inapp_cta_clicked", name: "UserGist In-App Message CTA Clicked", category: "inapp", subjectRole: "actor", properties: { ...ids("message_id", "cta_id", "cta_index"), action: ["action", "cta_action"] } },
369
+ ...pushEvents.map(([key, name]) => ({
370
+ key,
371
+ name,
372
+ category: "push",
373
+ subjectRole: "actor",
374
+ properties: {
375
+ ...ids("delivery_id", "campaign_id", "delivery_state", "failure_code"),
376
+ action_id: ["action_id", "actionId", "action_button", "actionButton"]
377
+ }
378
+ })),
379
+ ...requestEvents.map(([key, name, subjectRole]) => ({ key, name, category: "requests", subjectRole, properties: { ...ids("request_id", "comment_id", "source", "old_status", "new_status", "deleted_by"), usergist_actor_type: ["usergist_actor_type", "actor_type"] } }))
380
+ ];
381
+ var CORE_EVENT_BY_KEY = new Map(CORE_INTEGRATION_EVENTS.map((event) => [event.key, event]));
382
+ function getCoreIntegrationEvent(key) {
383
+ return CORE_EVENT_BY_KEY.get(key) ?? null;
384
+ }
385
+ function sanitizeCoreIntegrationProperties(definition, input) {
386
+ if (!input) return {};
387
+ const output = {};
388
+ for (const [outputKey, aliases] of Object.entries(definition.properties)) {
389
+ const alias = aliases.find((candidate) => Object.prototype.hasOwnProperty.call(input, candidate));
390
+ if (!alias) continue;
391
+ const value = input[alias];
392
+ if (typeof value === "string") output[outputKey] = value.slice(0, 1024);
393
+ else if (typeof value === "number" && Number.isFinite(value)) output[outputKey] = value;
394
+ else if (typeof value === "boolean" || value === null) output[outputKey] = value;
395
+ }
396
+ return output;
397
+ }
398
+
399
+ // src/constants/push-events.ts
400
+ var PUSH_EVENTS = {
401
+ SENT: "$push_sent",
402
+ DELIVERED: "$push_delivered",
403
+ DISPLAYED: "$push_displayed",
404
+ RECEIVED: "$push_received",
405
+ OPENED: "$push_opened",
406
+ DISMISSED: "$push_dismissed",
407
+ CLICKED: "$push_clicked",
408
+ ACTION_CLICKED: "$push_action_clicked",
409
+ BOUNCED: "$push_bounced",
410
+ SILENT_ACK: "$push_silent_ack"
411
+ };
412
+
413
+ // src/types/push-runtime.ts
414
+ function isSilentPushPayload(raw) {
415
+ if (!raw) return false;
416
+ return raw.usergist_silent === "1" && typeof raw.usergist_ping_id === "string";
417
+ }
418
+
419
+ // src/evaluate/segment.ts
420
+ function evaluateSegment(dsl, user, now = Date.now()) {
421
+ return evaluateGroup(dsl.root, user, now);
422
+ }
423
+ function evaluateGroup(group, user, now) {
424
+ if (group.predicates.length === 0) return true;
425
+ if (group.combinator === "AND") {
426
+ return group.predicates.every((p) => evaluateNode(p, user, now));
427
+ }
428
+ return group.predicates.some((p) => evaluateNode(p, user, now));
429
+ }
430
+ function evaluateNode(node, user, now) {
431
+ if ("combinator" in node) return evaluateGroup(node, user, now);
432
+ switch (node.kind) {
433
+ case "user_property":
434
+ return evaluateUserProperty(node, user);
435
+ case "event_count":
436
+ return evaluateEventCount(node, user);
437
+ case "event_occurred":
438
+ return evaluateEventOccurred(node, user, now);
439
+ }
440
+ }
441
+ function evaluateUserProperty(p, user) {
442
+ const actual = user.properties[p.key];
443
+ switch (p.op) {
444
+ case "exists":
445
+ return actual !== void 0 && actual !== null;
446
+ case "not_exists":
447
+ return actual === void 0 || actual === null;
448
+ case "eq":
449
+ return actual === p.value;
450
+ case "neq":
451
+ return actual !== p.value;
452
+ case "in":
453
+ return Array.isArray(p.value) && actual != null && p.value.includes(actual);
454
+ case "nin":
455
+ return Array.isArray(p.value) && actual != null && !p.value.includes(actual);
456
+ case "gt":
457
+ return typeof actual === "number" && typeof p.value === "number" && actual > p.value;
458
+ case "gte":
459
+ return typeof actual === "number" && typeof p.value === "number" && actual >= p.value;
460
+ case "lt":
461
+ return typeof actual === "number" && typeof p.value === "number" && actual < p.value;
462
+ case "lte":
463
+ return typeof actual === "number" && typeof p.value === "number" && actual <= p.value;
464
+ case "contains":
465
+ return typeof actual === "string" && typeof p.value === "string" && actual.includes(p.value);
466
+ case "starts_with":
467
+ return typeof actual === "string" && typeof p.value === "string" && actual.startsWith(p.value);
468
+ }
469
+ }
470
+ function evaluateEventCount(p, user) {
471
+ const eventBucket = user.eventCounts[p.eventName];
472
+ const count = eventBucket?.[p.windowDays] ?? 0;
473
+ switch (p.op) {
474
+ case "eq":
475
+ return count === p.count;
476
+ case "gte":
477
+ return count >= p.count;
478
+ case "lte":
479
+ return count <= p.count;
480
+ case "gt":
481
+ return count > p.count;
482
+ case "lt":
483
+ return count < p.count;
484
+ }
485
+ }
486
+ function evaluateEventOccurred(p, user, now) {
487
+ const last = user.lastEventAt[p.eventName];
488
+ if (p.ever) {
489
+ if (!last) return false;
490
+ if (p.windowDays == null) return true;
491
+ const cutoff2 = now - p.windowDays * 864e5;
492
+ return Date.parse(last) >= cutoff2;
493
+ }
494
+ if (!last) return true;
495
+ if (p.windowDays == null) return false;
496
+ const cutoff = now - p.windowDays * 864e5;
497
+ return Date.parse(last) < cutoff;
498
+ }
499
+ function evaluateSerializedSegmentRules(rules, user, now = Date.now()) {
500
+ if (!rules) return true;
501
+ const dsl = serializedRulesToDsl(rules);
502
+ return evaluateSegment(dsl, user, now);
503
+ }
504
+ function serializedRulesToDsl(rules) {
505
+ const predicates = [];
506
+ for (const rule of rules.userProperties ?? []) {
507
+ predicates.push(userPropertyRuleToPredicate(rule));
508
+ }
509
+ for (const rule of rules.eventCounts ?? []) {
510
+ predicates.push({
511
+ kind: "event_count",
512
+ eventName: rule.eventName,
513
+ op: rule.op,
514
+ count: rule.count,
515
+ windowDays: rule.windowDays
516
+ });
517
+ }
518
+ return {
519
+ version: 1,
520
+ root: { combinator: "AND", predicates }
521
+ };
522
+ }
523
+ function userPropertyRuleToPredicate(rule) {
524
+ return {
525
+ kind: "user_property",
526
+ key: rule.key,
527
+ op: rule.op,
528
+ value: rule.value
529
+ };
530
+ }
531
+
532
+ // src/evaluate/branch.ts
533
+ function evaluateBranchCondition(cond, answer) {
534
+ if (cond.op === "answered") return answer !== void 0 && answer !== null && answer !== "";
535
+ if (cond.op === "unanswered") return answer === void 0 || answer === null || answer === "";
536
+ if (answer === void 0 || answer === null) return false;
537
+ switch (cond.op) {
538
+ case "eq":
539
+ return answer === cond.value;
540
+ case "neq":
541
+ return answer !== cond.value;
542
+ case "lt":
543
+ return typeof answer === "number" && typeof cond.value === "number" && answer < cond.value;
544
+ case "lte":
545
+ return typeof answer === "number" && typeof cond.value === "number" && answer <= cond.value;
546
+ case "gt":
547
+ return typeof answer === "number" && typeof cond.value === "number" && answer > cond.value;
548
+ case "gte":
549
+ return typeof answer === "number" && typeof cond.value === "number" && answer >= cond.value;
550
+ case "includes":
551
+ return Array.isArray(answer) && answer.includes(cond.value);
552
+ case "not_includes":
553
+ return Array.isArray(answer) && !answer.includes(cond.value);
554
+ default:
555
+ return false;
556
+ }
557
+ }
558
+ function nextQuestionId(flow, currentQuestionId, answers) {
559
+ const branches = flow.branches.filter((b) => b.fromQuestionId === currentQuestionId);
560
+ const answer = answers[currentQuestionId];
561
+ for (const branch of branches) {
562
+ if (evaluateBranchCondition(branch.condition, answer)) {
563
+ if (branch.toQuestionId === SURVEY_END_SENTINEL) return null;
564
+ return branch.toQuestionId;
565
+ }
566
+ }
567
+ return defaultNext(flow, currentQuestionId);
568
+ }
569
+ function defaultNext(flow, currentQuestionId) {
570
+ const idx = flow.questions.findIndex((q) => q.id === currentQuestionId);
571
+ if (idx < 0 || idx >= flow.questions.length - 1) return null;
572
+ const next = flow.questions[idx + 1];
573
+ return next ? next.id : null;
574
+ }
575
+ function findQuestion(flow, questionId) {
576
+ return flow.questions.find((q) => q.id === questionId);
577
+ }
578
+ function findQuestionIndex(flow, questionId) {
579
+ return flow.questions.findIndex((q) => q.id === questionId);
580
+ }
581
+ function estimateProgress(flow, currentQuestionId) {
582
+ if (!currentQuestionId) return 1;
583
+ const idx = findQuestionIndex(flow, currentQuestionId);
584
+ if (idx < 0) return 0;
585
+ return (idx + 1) / Math.max(1, flow.questions.length);
586
+ }
587
+ function reachableQuestions(flow, startId) {
588
+ const start = startId ?? flow.startQuestionId;
589
+ const visited = /* @__PURE__ */ new Set();
590
+ const stack = [start];
591
+ while (stack.length > 0) {
592
+ const id = stack.pop();
593
+ if (id === void 0) continue;
594
+ if (visited.has(id)) continue;
595
+ visited.add(id);
596
+ const outgoing = flow.branches.filter((b) => b.fromQuestionId === id);
597
+ for (const b of outgoing) {
598
+ if (b.toQuestionId !== SURVEY_END_SENTINEL) stack.push(b.toQuestionId);
599
+ }
600
+ const idx = flow.questions.findIndex((q) => q.id === id);
601
+ if (idx >= 0 && idx < flow.questions.length - 1) {
602
+ const next = flow.questions[idx + 1];
603
+ if (next) stack.push(next.id);
604
+ }
605
+ }
606
+ return visited;
607
+ }
608
+
609
+ // src/evaluate/periodic.ts
610
+ function nextPeriodicFire(schedule, now = /* @__PURE__ */ new Date()) {
611
+ const tz = schedule.tz || "UTC";
612
+ const start = startCandidate(schedule, now, tz);
613
+ return walkForward(start, schedule, now, tz);
614
+ }
615
+ function tzOffsetMinutes(at, tz) {
616
+ if (tz === "UTC") return 0;
617
+ try {
618
+ const fmt = getFormatter(tz);
619
+ const parts = fmt.formatToParts(at).reduce((acc, p) => {
620
+ if (p.type !== "literal") acc[p.type] = p.value;
621
+ return acc;
622
+ }, {});
623
+ const utc = Date.UTC(
624
+ Number(parts.year),
625
+ Number(parts.month) - 1,
626
+ Number(parts.day),
627
+ Number(parts.hour) === 24 ? 0 : Number(parts.hour),
628
+ Number(parts.minute),
629
+ Number(parts.second)
630
+ );
631
+ return Math.round((utc - at.getTime()) / 6e4);
632
+ } catch {
633
+ return 0;
634
+ }
635
+ }
636
+ function startCandidate(schedule, now, tz) {
637
+ const localNow = wallClock(now, tz);
638
+ const target = new Date(localNow);
639
+ target.setHours(schedule.hourLocal, schedule.minuteLocal, 0, 0);
640
+ return localToUtc(target, tz);
641
+ }
642
+ function walkForward(candidate, schedule, now, tz) {
643
+ let cursor = candidate;
644
+ const max = schedule.frequency === "daily" ? 2 : schedule.frequency === "weekly" ? 8 : 13;
645
+ for (let i = 0; i < max; i++) {
646
+ const local = wallClock(cursor, tz);
647
+ if (matchesFrequency(local, schedule) && cursor.getTime() > now.getTime()) {
648
+ return cursor;
649
+ }
650
+ cursor = step(cursor, local, schedule, tz);
651
+ }
652
+ return cursor;
653
+ }
654
+ function matchesFrequency(local, schedule) {
655
+ if (schedule.frequency === "daily") return true;
656
+ if (schedule.frequency === "weekly") {
657
+ return schedule.weekday === void 0 || local.getDay() === schedule.weekday;
658
+ }
659
+ return schedule.dayOfMonth === void 0 || local.getDate() === schedule.dayOfMonth;
660
+ }
661
+ function step(cursor, cursorLocal, schedule, tz) {
662
+ if (schedule.frequency === "daily" || schedule.frequency === "weekly") {
663
+ return addDays(cursor, 1);
664
+ }
665
+ const next = new Date(cursorLocal);
666
+ next.setMonth(next.getMonth() + 1);
667
+ if (schedule.dayOfMonth !== void 0) {
668
+ const lastDay = lastDayOfMonth(next.getFullYear(), next.getMonth());
669
+ next.setDate(Math.min(schedule.dayOfMonth, lastDay));
670
+ }
671
+ next.setHours(schedule.hourLocal, schedule.minuteLocal, 0, 0);
672
+ return localToUtc(next, tz);
673
+ }
674
+ function addDays(d, days) {
675
+ const next = new Date(d.getTime());
676
+ next.setDate(next.getDate() + days);
677
+ return next;
678
+ }
679
+ function lastDayOfMonth(year, monthIndex) {
680
+ return new Date(year, monthIndex + 1, 0).getDate();
681
+ }
682
+ function wallClock(d, tz) {
683
+ const offsetMin = tzOffsetMinutes(d, tz);
684
+ return new Date(d.getTime() + offsetMin * 6e4);
685
+ }
686
+ function localToUtc(localView, tz) {
687
+ const offsetMin = tzOffsetMinutes(localView, tz);
688
+ return new Date(localView.getTime() - offsetMin * 6e4);
689
+ }
690
+ var FORMATTER_CACHE = /* @__PURE__ */ new Map();
691
+ function getFormatter(tz) {
692
+ const cached = FORMATTER_CACHE.get(tz);
693
+ if (cached) return cached;
694
+ const fmt = new Intl.DateTimeFormat("en-US", {
695
+ timeZone: tz,
696
+ hour12: false,
697
+ year: "numeric",
698
+ month: "2-digit",
699
+ day: "2-digit",
700
+ hour: "2-digit",
701
+ minute: "2-digit",
702
+ second: "2-digit"
703
+ });
704
+ FORMATTER_CACHE.set(tz, fmt);
705
+ return fmt;
706
+ }
707
+
708
+ export { APP_OPEN_EVENT_NAME, APP_VERSION_CHANGED_EVENT_NAME, AUDIENCE_JOIN_EVENT_NAME, AUDIENCE_JOIN_TRIGGER_KIND, CORE_INTEGRATION_EVENTS, INAPP_AUTO_DISMISSED_EVENT_NAME, INAPP_CTA_CLICKED_EVENT_NAME, INAPP_DISMISSED_EVENT_NAME, INAPP_SHOWN_EVENT_NAME, INTEGRATION_CATEGORIES, INTEGRATION_PROVIDERS, PUSH_EVENTS, RADIUS_PRESETS, REQUEST_COMMENTED_EVENT_NAME, REQUEST_COMMENT_DELETED_EVENT_NAME, REQUEST_COMMENT_EDITED_EVENT_NAME, REQUEST_FOLLOWED_EVENT_NAME, REQUEST_RESPONDED_EVENT_NAME, REQUEST_STATUS_CHANGED_EVENT_NAME, REQUEST_SUBMITTED_EVENT_NAME, REQUEST_UNFOLLOWED_EVENT_NAME, REQUEST_UNUPVOTED_EVENT_NAME, REQUEST_UPVOTED_EVENT_NAME, SURVEY_END_SENTINEL, THEME_PRESETS, USER_IDENTIFIED_EVENT_NAME, brandTokensFromPreset, defaultEventTrigger, defaultThemePreset, defaultTriggerSpec, emptyAudienceSpec, emptySegmentDsl, err, estimateProgress, evaluateBranchCondition, evaluateSegment, evaluateSerializedSegmentRules, findQuestion, findQuestionIndex, getCoreIntegrationEvent, getThemePresetById, inAppColorsFromBrandTokens, isSilentPushPayload, nextPeriodicFire, nextQuestionId, ok, promptThemeFromBrandTokens, reachableQuestions, requestAccentFromBrandTokens, sanitizeCoreIntegrationProperties, serializedRulesToDsl, tzOffsetMinutes };
709
+ //# sourceMappingURL=mobile.js.map
710
+ //# sourceMappingURL=mobile.js.map