@reform-society/agera-core 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +81 -3
- package/dist/index.js +159 -16
- package/package.json +8 -6
package/dist/index.d.ts
CHANGED
|
@@ -204,6 +204,8 @@ declare function calculateSectionResults(questions: QuizQuestionInput[], questio
|
|
|
204
204
|
declare function scoreQuiz(questions: QuizQuestionInput[], config?: QuizConfig): QuizResults;
|
|
205
205
|
//#endregion
|
|
206
206
|
//#region src/basin-client.d.ts
|
|
207
|
+
type JwtPayload = Record<string, unknown>;
|
|
208
|
+
declare function decodeJwtPayload(jwt: string): JwtPayload | null;
|
|
207
209
|
/** Check if JWT is expired (with 60s buffer) */
|
|
208
210
|
declare function isJwtExpired(jwt: string): boolean;
|
|
209
211
|
type StorageAccessor = {
|
|
@@ -211,7 +213,10 @@ type StorageAccessor = {
|
|
|
211
213
|
setItem(key: string, value: string): void;
|
|
212
214
|
removeItem(key: string): void;
|
|
213
215
|
};
|
|
214
|
-
|
|
216
|
+
type FetchBasinJWTOptions = {
|
|
217
|
+
isCachedJwtValid?: (jwt: string) => boolean;
|
|
218
|
+
};
|
|
219
|
+
declare function fetchBasinJWT(endpoint: string, storage: StorageAccessor, storageKey: string, options?: FetchBasinJWTOptions): Promise<string>;
|
|
215
220
|
//#endregion
|
|
216
221
|
//#region src/crm-types.d.ts
|
|
217
222
|
type CounterUpdateConfig = {
|
|
@@ -262,11 +267,55 @@ type CrmV2Request = {
|
|
|
262
267
|
type CrmV2PatchRequest = CrmV2Request & {
|
|
263
268
|
response: Record<string, unknown>;
|
|
264
269
|
};
|
|
270
|
+
type CrmV2Payload = CrmV2Request & {
|
|
271
|
+
response?: Record<string, unknown>;
|
|
272
|
+
};
|
|
265
273
|
type CrmFetchResult = {
|
|
266
274
|
ok: boolean;
|
|
267
275
|
status: number;
|
|
268
276
|
data: Record<string, unknown> | null;
|
|
269
277
|
};
|
|
278
|
+
type CrmCommand = {
|
|
279
|
+
client: string;
|
|
280
|
+
payload: CrmV2Payload;
|
|
281
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
282
|
+
delivery?: "wait" | "background";
|
|
283
|
+
};
|
|
284
|
+
type CrmDispatch = {
|
|
285
|
+
client: string;
|
|
286
|
+
method: "POST" | "PUT" | "PATCH";
|
|
287
|
+
delivery: "wait" | "background";
|
|
288
|
+
payload: CrmV2Payload;
|
|
289
|
+
signal?: AbortSignal;
|
|
290
|
+
};
|
|
291
|
+
type CrmGatewayResult = {
|
|
292
|
+
kind: "response";
|
|
293
|
+
response: CrmFetchResult;
|
|
294
|
+
} | {
|
|
295
|
+
kind: "accepted";
|
|
296
|
+
};
|
|
297
|
+
type CrmGateway = (dispatch: CrmDispatch) => Promise<CrmGatewayResult>;
|
|
298
|
+
type CrmOutcome = {
|
|
299
|
+
status: "succeeded";
|
|
300
|
+
httpStatus: number;
|
|
301
|
+
response: Record<string, unknown> | null;
|
|
302
|
+
} | {
|
|
303
|
+
status: "accepted";
|
|
304
|
+
} | {
|
|
305
|
+
status: "rejected";
|
|
306
|
+
httpStatus: number;
|
|
307
|
+
response: Record<string, unknown> | null;
|
|
308
|
+
} | {
|
|
309
|
+
status: "failed";
|
|
310
|
+
cause: unknown;
|
|
311
|
+
} | {
|
|
312
|
+
status: "aborted";
|
|
313
|
+
};
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/crm-submission.d.ts
|
|
316
|
+
declare function executeCrm(gateway: CrmGateway, command: CrmCommand, options?: {
|
|
317
|
+
signal?: AbortSignal;
|
|
318
|
+
}): Promise<CrmOutcome>;
|
|
270
319
|
//#endregion
|
|
271
320
|
//#region src/utm.d.ts
|
|
272
321
|
declare const UTM_KEYS: readonly ["source", "campaign", "medium", "content", "term", "wec"];
|
|
@@ -347,6 +396,19 @@ type DeviceInput = {
|
|
|
347
396
|
};
|
|
348
397
|
declare function detectDeviceType(input: DeviceInput): DeviceType;
|
|
349
398
|
//#endregion
|
|
399
|
+
//#region src/visitor.d.ts
|
|
400
|
+
declare const VISITOR_ID_STORAGE_KEY = "agera:vid";
|
|
401
|
+
/**
|
|
402
|
+
* Read the semi-persistent pseudonymous visitor id from storage, generating
|
|
403
|
+
* and persisting a new one when absent. Used only to correlate requests from
|
|
404
|
+
* the same browser in crm-api logs — never stored server-side.
|
|
405
|
+
*
|
|
406
|
+
* Returns null when storage can't be read or written (private mode, quota,
|
|
407
|
+
* blocked storage): an unpersisted id would look like a new visitor on every
|
|
408
|
+
* page load, so callers omit the header instead.
|
|
409
|
+
*/
|
|
410
|
+
declare function getOrCreateVisitorId(storage: StorageAccessor): string | null;
|
|
411
|
+
//#endregion
|
|
350
412
|
//#region src/crm-fetch.d.ts
|
|
351
413
|
type CrmFetchOptions = {
|
|
352
414
|
waitForResponse?: boolean;
|
|
@@ -356,6 +418,22 @@ type CrmFetchOptions = {
|
|
|
356
418
|
};
|
|
357
419
|
declare function crmFetch(url: string, payload: Record<string, unknown> | object, options?: CrmFetchOptions): Promise<CrmFetchResult>;
|
|
358
420
|
//#endregion
|
|
421
|
+
//#region src/crm-fields.d.ts
|
|
422
|
+
declare const CRM_CONTACT_KEYS: readonly ["given-name", "family-name", "email", "tel", "street-address", "address-level2", "postal-code", "country"];
|
|
423
|
+
type CrmContactKey = (typeof CRM_CONTACT_KEYS)[number];
|
|
424
|
+
declare function isCrmContactKey(key: string): key is CrmContactKey;
|
|
425
|
+
type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
426
|
+
readonly [key: string]: JsonValue;
|
|
427
|
+
};
|
|
428
|
+
type CrmGroupedAnswer = {
|
|
429
|
+
group: string;
|
|
430
|
+
name: string;
|
|
431
|
+
value: JsonValue;
|
|
432
|
+
};
|
|
433
|
+
type CrmJsonGroups = Record<string, Record<string, JsonValue>>;
|
|
434
|
+
declare function groupCrmAnswers(answers: readonly CrmGroupedAnswer[]): CrmJsonGroups;
|
|
435
|
+
declare function serializeCrmJsonGroups(groups: CrmJsonGroups): string | undefined;
|
|
436
|
+
//#endregion
|
|
359
437
|
//#region src/crm-request.d.ts
|
|
360
438
|
type AssembleCrmRequestParams = {
|
|
361
439
|
body: Record<string, unknown>;
|
|
@@ -365,7 +443,7 @@ type AssembleCrmRequestParams = {
|
|
|
365
443
|
queryParams: Record<string, string>;
|
|
366
444
|
sourceUrl: string;
|
|
367
445
|
counters?: CounterUpdateConfig[];
|
|
368
|
-
jsonGroups?:
|
|
446
|
+
jsonGroups?: CrmJsonGroups;
|
|
369
447
|
};
|
|
370
448
|
declare function assembleCrmRequest({
|
|
371
449
|
body: rawBody,
|
|
@@ -381,5 +459,5 @@ declare function assembleCrmRequest({
|
|
|
381
459
|
//#region src/polling.d.ts
|
|
382
460
|
declare function matchesFormat(example: unknown, value: unknown): boolean;
|
|
383
461
|
//#endregion
|
|
384
|
-
export { type AssembleCrmRequestParams, type BandQuizResults, type CompositePredicate, type CounterDisplayConfig, type CounterPayload, type CounterResponse, type CounterUpdateConfig, type CrmConfig, type CrmFetchOptions, type CrmFetchResult, type CrmFrontendKey, type CrmV2PatchRequest, type CrmV2Request, type DeviceInput, type DeviceType, type ErrorMessages, type InsertDirective, type MemoryCache, type MemoryCacheOptions, type MemoryCacheSetOptions, type PassQuizResults, type PlacesMessageKey, type PlacesMessages, type Predicate, type PredicateOp, type ProgressInfo, type ProgressModeInfo, type QuestionMetrics, type QuizBandDefinition, type QuizConfig, type QuizOutcomeModel, type QuizPassFailConfig, type QuizQuestionInput, type QuizQuestionModel, type QuizQuestionResult, type QuizQuestionState, type QuizResults, type QuizScoringModel, type QuizSectionConfig, type QuizSectionResults, type ResolveValueSources, type ScoreQuizResults, type SessionState, type ShareConfig, type SimplePredicate, type SplitConfig, type StepState, type StorageAccessor, type SurveyOptions, type Utms, type ValidationRule, type Validators, type WeightedQuizResults, aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, inferCounterName, isCounterResponse, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, updateUrlWithParams, validators };
|
|
462
|
+
export { type AssembleCrmRequestParams, type BandQuizResults, CRM_CONTACT_KEYS, type CompositePredicate, type CounterDisplayConfig, type CounterPayload, type CounterResponse, type CounterUpdateConfig, type CrmCommand, type CrmConfig, type CrmContactKey, type CrmDispatch, type CrmFetchOptions, type CrmFetchResult, type CrmFrontendKey, type CrmGateway, type CrmGatewayResult, type CrmGroupedAnswer, type CrmJsonGroups, type CrmOutcome, type CrmV2PatchRequest, type CrmV2Payload, type CrmV2Request, type DeviceInput, type DeviceType, type ErrorMessages, type FetchBasinJWTOptions, type InsertDirective, type JsonValue, type JwtPayload, type MemoryCache, type MemoryCacheOptions, type MemoryCacheSetOptions, type PassQuizResults, type PlacesMessageKey, type PlacesMessages, type Predicate, type PredicateOp, type ProgressInfo, type ProgressModeInfo, type QuestionMetrics, type QuizBandDefinition, type QuizConfig, type QuizOutcomeModel, type QuizPassFailConfig, type QuizQuestionInput, type QuizQuestionModel, type QuizQuestionResult, type QuizQuestionState, type QuizResults, type QuizScoringModel, type QuizSectionConfig, type QuizSectionResults, type ResolveValueSources, type ScoreQuizResults, type SessionState, type ShareConfig, type SimplePredicate, type SplitConfig, type StepState, type StorageAccessor, type SurveyOptions, type Utms, VISITOR_ID_STORAGE_KEY, type ValidationRule, type Validators, type WeightedQuizResults, aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, decodeJwtPayload, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, executeCrm, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getOrCreateVisitorId, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, groupCrmAnswers, inferCounterName, isCounterResponse, isCrmContactKey, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, serializeCrmJsonGroups, updateUrlWithParams, validators };
|
|
385
463
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -111,9 +111,26 @@ function evaluatePredicate(predicate, answers, sources) {
|
|
|
111
111
|
}
|
|
112
112
|
return false;
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Parse predicate JSON, tolerating single-quoted JSON.
|
|
116
|
+
*
|
|
117
|
+
* Webflow's Designer API (setCustomAttribute) forbids double quotes in
|
|
118
|
+
* attribute values, so predicates authored via the chat/Logic tab use single
|
|
119
|
+
* quotes — e.g. {'field':'plan','op':'eq','value':'pro'}. Strict JSON is tried
|
|
120
|
+
* first (fully backwards compatible); single-quote normalization is a fallback.
|
|
121
|
+
*/
|
|
122
|
+
function parsePredicateJson(json) {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(json);
|
|
125
|
+
} catch {
|
|
126
|
+
const sentinel = String.fromCharCode(0);
|
|
127
|
+
const normalized = json.replace(/\\'/g, sentinel).replace(/'/g, "\"").split(sentinel).join("'");
|
|
128
|
+
return JSON.parse(normalized);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
114
131
|
function evaluateShowIf(predicateJson, answers, sources) {
|
|
115
132
|
try {
|
|
116
|
-
return evaluatePredicate(
|
|
133
|
+
return evaluatePredicate(parsePredicateJson(predicateJson), answers, sources);
|
|
117
134
|
} catch (error) {
|
|
118
135
|
log.error("[conditions] Invalid predicate JSON:", error);
|
|
119
136
|
return false;
|
|
@@ -121,7 +138,7 @@ function evaluateShowIf(predicateJson, answers, sources) {
|
|
|
121
138
|
}
|
|
122
139
|
function evaluateNextMap(nextMapJson, answers, sources) {
|
|
123
140
|
try {
|
|
124
|
-
const nextMap =
|
|
141
|
+
const nextMap = parsePredicateJson(nextMapJson);
|
|
125
142
|
for (const rule of nextMap) if (evaluatePredicate(rule.if, answers, sources)) return rule.then;
|
|
126
143
|
return null;
|
|
127
144
|
} catch (error) {
|
|
@@ -664,21 +681,55 @@ function scoreQuiz(questions, config = {}) {
|
|
|
664
681
|
}
|
|
665
682
|
//#endregion
|
|
666
683
|
//#region src/basin-client.ts
|
|
667
|
-
|
|
668
|
-
|
|
684
|
+
function decodeBase64Url(value) {
|
|
685
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
686
|
+
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
|
|
687
|
+
return atob(padded);
|
|
688
|
+
}
|
|
689
|
+
function decodeJwtPayload(jwt) {
|
|
669
690
|
try {
|
|
670
691
|
const payload = jwt.split(".")[1];
|
|
671
|
-
if (!payload) return
|
|
672
|
-
const
|
|
673
|
-
|
|
692
|
+
if (!payload) return null;
|
|
693
|
+
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
694
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
695
|
+
return parsed;
|
|
674
696
|
} catch {
|
|
675
|
-
return
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
/** Check if JWT is expired (with 60s buffer) */
|
|
701
|
+
function isJwtExpired(jwt) {
|
|
702
|
+
const payload = decodeJwtPayload(jwt);
|
|
703
|
+
if (!payload) return true;
|
|
704
|
+
if (typeof payload.exp !== "number") return true;
|
|
705
|
+
return payload.exp <= Date.now() / 1e3 + 60;
|
|
706
|
+
}
|
|
707
|
+
function getStoredJwt(storage, storageKey) {
|
|
708
|
+
try {
|
|
709
|
+
return storage.getItem(storageKey);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
log.debug("[basin] Failed to read cached JWT:", error);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function removeStoredJwt(storage, storageKey) {
|
|
716
|
+
try {
|
|
717
|
+
storage.removeItem(storageKey);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
log.debug("[basin] Failed to remove cached JWT:", error);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function setStoredJwt(storage, storageKey, jwt) {
|
|
723
|
+
try {
|
|
724
|
+
storage.setItem(storageKey, jwt);
|
|
725
|
+
} catch (error) {
|
|
726
|
+
log.debug("[basin] Failed to cache JWT:", error);
|
|
676
727
|
}
|
|
677
728
|
}
|
|
678
|
-
async function fetchBasinJWT(endpoint, storage, storageKey) {
|
|
679
|
-
const cached = storage
|
|
680
|
-
if (cached && !isJwtExpired(cached)) return cached;
|
|
681
|
-
if (cached) storage
|
|
729
|
+
async function fetchBasinJWT(endpoint, storage, storageKey, options = {}) {
|
|
730
|
+
const cached = getStoredJwt(storage, storageKey);
|
|
731
|
+
if (cached && !isJwtExpired(cached) && (options.isCachedJwtValid?.(cached) ?? true)) return cached;
|
|
732
|
+
if (cached) removeStoredJwt(storage, storageKey);
|
|
682
733
|
try {
|
|
683
734
|
const res = await fetch(`${endpoint}/generate_jwt`, {
|
|
684
735
|
method: "GET",
|
|
@@ -693,7 +744,7 @@ async function fetchBasinJWT(endpoint, storage, storageKey) {
|
|
|
693
744
|
}
|
|
694
745
|
const data = await res.json();
|
|
695
746
|
if (data.jwt) {
|
|
696
|
-
storage
|
|
747
|
+
setStoredJwt(storage, storageKey, data.jwt);
|
|
697
748
|
return data.jwt;
|
|
698
749
|
}
|
|
699
750
|
return "";
|
|
@@ -703,6 +754,41 @@ async function fetchBasinJWT(endpoint, storage, storageKey) {
|
|
|
703
754
|
}
|
|
704
755
|
}
|
|
705
756
|
//#endregion
|
|
757
|
+
//#region src/crm-submission.ts
|
|
758
|
+
function isAbortError(error) {
|
|
759
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
760
|
+
}
|
|
761
|
+
async function executeCrm(gateway, command, options = {}) {
|
|
762
|
+
const { signal } = options;
|
|
763
|
+
if (signal?.aborted) return { status: "aborted" };
|
|
764
|
+
try {
|
|
765
|
+
const result = await gateway({
|
|
766
|
+
client: command.client,
|
|
767
|
+
method: command.method ?? "POST",
|
|
768
|
+
delivery: command.delivery ?? "wait",
|
|
769
|
+
payload: command.payload,
|
|
770
|
+
signal
|
|
771
|
+
});
|
|
772
|
+
if (result.kind === "accepted") return { status: "accepted" };
|
|
773
|
+
const { ok, status, data } = result.response;
|
|
774
|
+
return ok ? {
|
|
775
|
+
status: "succeeded",
|
|
776
|
+
httpStatus: status,
|
|
777
|
+
response: data
|
|
778
|
+
} : {
|
|
779
|
+
status: "rejected",
|
|
780
|
+
httpStatus: status,
|
|
781
|
+
response: data
|
|
782
|
+
};
|
|
783
|
+
} catch (error) {
|
|
784
|
+
if (signal?.aborted || isAbortError(error)) return { status: "aborted" };
|
|
785
|
+
return {
|
|
786
|
+
status: "failed",
|
|
787
|
+
cause: error
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
//#endregion
|
|
706
792
|
//#region src/utm.ts
|
|
707
793
|
const UTM_KEYS = [
|
|
708
794
|
"source",
|
|
@@ -1029,6 +1115,32 @@ function detectDeviceType(input) {
|
|
|
1029
1115
|
return "desktop";
|
|
1030
1116
|
}
|
|
1031
1117
|
//#endregion
|
|
1118
|
+
//#region src/visitor.ts
|
|
1119
|
+
const VISITOR_ID_STORAGE_KEY = "agera:vid";
|
|
1120
|
+
function generateVisitorId() {
|
|
1121
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Read the semi-persistent pseudonymous visitor id from storage, generating
|
|
1125
|
+
* and persisting a new one when absent. Used only to correlate requests from
|
|
1126
|
+
* the same browser in crm-api logs — never stored server-side.
|
|
1127
|
+
*
|
|
1128
|
+
* Returns null when storage can't be read or written (private mode, quota,
|
|
1129
|
+
* blocked storage): an unpersisted id would look like a new visitor on every
|
|
1130
|
+
* page load, so callers omit the header instead.
|
|
1131
|
+
*/
|
|
1132
|
+
function getOrCreateVisitorId(storage) {
|
|
1133
|
+
try {
|
|
1134
|
+
const existing = storage.getItem(VISITOR_ID_STORAGE_KEY);
|
|
1135
|
+
if (existing) return existing;
|
|
1136
|
+
const id = generateVisitorId();
|
|
1137
|
+
storage.setItem(VISITOR_ID_STORAGE_KEY, id);
|
|
1138
|
+
return id;
|
|
1139
|
+
} catch {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
//#endregion
|
|
1032
1144
|
//#region src/crm-fetch.ts
|
|
1033
1145
|
const noop = () => {};
|
|
1034
1146
|
async function crmFetch(url, payload, options = {}) {
|
|
@@ -1076,14 +1188,45 @@ async function crmFetch(url, payload, options = {}) {
|
|
|
1076
1188
|
}
|
|
1077
1189
|
}
|
|
1078
1190
|
//#endregion
|
|
1191
|
+
//#region src/crm-fields.ts
|
|
1192
|
+
const CRM_CONTACT_KEYS = [
|
|
1193
|
+
"given-name",
|
|
1194
|
+
"family-name",
|
|
1195
|
+
"email",
|
|
1196
|
+
"tel",
|
|
1197
|
+
"street-address",
|
|
1198
|
+
"address-level2",
|
|
1199
|
+
"postal-code",
|
|
1200
|
+
"country"
|
|
1201
|
+
];
|
|
1202
|
+
const crmContactKeySet = new Set(CRM_CONTACT_KEYS);
|
|
1203
|
+
function isCrmContactKey(key) {
|
|
1204
|
+
return crmContactKeySet.has(key);
|
|
1205
|
+
}
|
|
1206
|
+
function groupCrmAnswers(answers) {
|
|
1207
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1208
|
+
for (const { group, name, value } of answers) {
|
|
1209
|
+
if (group === "" || name === "") continue;
|
|
1210
|
+
const fields = groups.get(group) ?? /* @__PURE__ */ new Map();
|
|
1211
|
+
fields.set(name, value);
|
|
1212
|
+
groups.set(group, fields);
|
|
1213
|
+
}
|
|
1214
|
+
return Object.fromEntries([...groups].map(([group, fields]) => [group, Object.fromEntries(fields)]));
|
|
1215
|
+
}
|
|
1216
|
+
function serializeCrmJsonGroups(groups) {
|
|
1217
|
+
const groupEntries = Object.entries(groups);
|
|
1218
|
+
if (groupEntries.length === 0) return void 0;
|
|
1219
|
+
return JSON.stringify(groupEntries.map(([key, fields]) => ({ [key]: fields })));
|
|
1220
|
+
}
|
|
1221
|
+
//#endregion
|
|
1079
1222
|
//#region src/crm-request.ts
|
|
1080
1223
|
function assembleCrmRequest({ body: rawBody, config, frontendKeys, utms, queryParams, sourceUrl, counters, jsonGroups }) {
|
|
1081
1224
|
const configuredCounters = Array.isArray(config.counter_update) ? config.counter_update : void 0;
|
|
1082
1225
|
const resolvedCounters = counters ?? configuredCounters;
|
|
1083
1226
|
const body = { ...rawBody };
|
|
1084
1227
|
if (jsonGroups) {
|
|
1085
|
-
const
|
|
1086
|
-
if (
|
|
1228
|
+
const serialized = serializeCrmJsonGroups(jsonGroups);
|
|
1229
|
+
if (serialized !== void 0) body["json-groups"] = serialized;
|
|
1087
1230
|
}
|
|
1088
1231
|
return {
|
|
1089
1232
|
body,
|
|
@@ -1117,6 +1260,6 @@ function matchesFormat(example, value) {
|
|
|
1117
1260
|
return example === value;
|
|
1118
1261
|
}
|
|
1119
1262
|
//#endregion
|
|
1120
|
-
export { aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, inferCounterName, isCounterResponse, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, updateUrlWithParams, validators };
|
|
1263
|
+
export { CRM_CONTACT_KEYS, VISITOR_ID_STORAGE_KEY, aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, decodeJwtPayload, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, executeCrm, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getOrCreateVisitorId, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, groupCrmAnswers, inferCounterName, isCounterResponse, isCrmContactKey, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, serializeCrmJsonGroups, updateUrlWithParams, validators };
|
|
1121
1264
|
|
|
1122
1265
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reform-society/agera-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "DOM-free core logic for Agera",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/reform-society/agera
|
|
20
|
+
"url": "git+https://github.com/reform-society/agera.git",
|
|
21
21
|
"directory": "packages/agera-core"
|
|
22
22
|
},
|
|
23
23
|
"author": "Reform Society",
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
"test": "vitest run"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
37
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
38
|
+
"tsdown": "^0.21.10",
|
|
39
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
40
|
+
"vite": "7.3.5",
|
|
41
|
+
"vitest": "^4.1.11"
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
42
44
|
"loglevel": "^1.9.2",
|
|
43
|
-
"nanostores": "^1.
|
|
45
|
+
"nanostores": "^1.5.2"
|
|
44
46
|
}
|
|
45
47
|
}
|