@learncard/partner-connect 0.2.14 → 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/README.md +96 -0
- package/dist/index.d.ts +421 -40
- package/dist/partner-connect.esm.js +326 -43
- package/dist/partner-connect.esm.js.map +1 -1
- package/dist/partner-connect.js +326 -42
- package/dist/partner-connect.js.map +1 -1
- package/package.json +6 -4
package/dist/partner-connect.js
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var __defProp$1 = Object.defineProperty;
|
|
6
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
|
+
class PartnerConnectError extends Error {
|
|
9
|
+
constructor(code, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
__publicField$1(this, "code");
|
|
12
|
+
this.name = "PartnerConnectError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
Object.setPrototypeOf(this, PartnerConnectError.prototype);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Wrap any incoming `LearnCardError`-shaped value into a `PartnerConnectError`.
|
|
18
|
+
* Returns the value unchanged if it is already an instance.
|
|
19
|
+
*
|
|
20
|
+
* Used internally at every reject site so callers always receive a typed
|
|
21
|
+
* `PartnerConnectError`, regardless of whether the failure originated from
|
|
22
|
+
* the host (over postMessage), an SDK timeout, or `destroy()`.
|
|
23
|
+
*/
|
|
24
|
+
static from(input) {
|
|
25
|
+
if (input instanceof PartnerConnectError) return input;
|
|
26
|
+
if (input && typeof input === "object" && "code" in input && typeof input.code === "string") {
|
|
27
|
+
const candidate = input;
|
|
28
|
+
const message = typeof candidate.message === "string" ? candidate.message : "Partner Connect request failed";
|
|
29
|
+
return new PartnerConnectError(candidate.code, message);
|
|
30
|
+
}
|
|
31
|
+
return new PartnerConnectError(
|
|
32
|
+
"UNKNOWN_ERROR",
|
|
33
|
+
input instanceof Error ? input.message : "An unknown error occurred"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
5
38
|
var __defProp = Object.defineProperty;
|
|
6
39
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
40
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -15,62 +48,160 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
15
48
|
__publicField(this, "pendingRequests");
|
|
16
49
|
__publicField(this, "messageListener", null);
|
|
17
50
|
__publicField(this, "isInitialized", false);
|
|
18
|
-
var _a;
|
|
19
|
-
const hostOrigin = (options == null ? void 0 : options.hostOrigin)
|
|
20
|
-
|
|
51
|
+
var _a, _b;
|
|
52
|
+
const hostOrigin = (_a = options == null ? void 0 : options.hostOrigin) != null ? _a : _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
53
|
+
const configured = Array.isArray(hostOrigin) ? hostOrigin : [hostOrigin];
|
|
54
|
+
const disableDefaults = (options == null ? void 0 : options.disableDefaultTenants) === true;
|
|
55
|
+
const merged = disableDefaults ? [...configured] : [...configured, ..._PartnerConnect.DEFAULT_TRUSTED_TENANTS];
|
|
56
|
+
this.hostOrigins = Array.from(new Set(merged));
|
|
21
57
|
this.protocol = (options == null ? void 0 : options.protocol) || "LEARNCARD_V1";
|
|
22
58
|
this.requestTimeout = (options == null ? void 0 : options.requestTimeout) || 3e4;
|
|
23
|
-
this.allowNativeAppOrigins = (
|
|
59
|
+
this.allowNativeAppOrigins = (_b = options == null ? void 0 : options.allowNativeAppOrigins) != null ? _b : true;
|
|
24
60
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
25
61
|
this.configureActiveOrigin();
|
|
26
62
|
this.setupMessageListener();
|
|
27
63
|
}
|
|
28
64
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* 3. Fall back to DEFAULT_HOST_ORIGIN
|
|
33
|
-
*
|
|
34
|
-
* This origin will be used for all outgoing messages and incoming message validation.
|
|
65
|
+
* Read `window.location.ancestorOrigins[0]` without throwing if the
|
|
66
|
+
* property is unavailable (Firefox) or the list is empty (top-level
|
|
67
|
+
* context, e.g. running outside of an iframe).
|
|
35
68
|
*/
|
|
69
|
+
readAncestorOrigin() {
|
|
70
|
+
if (typeof window === "undefined") return null;
|
|
71
|
+
try {
|
|
72
|
+
const ancestors = window.location.ancestorOrigins;
|
|
73
|
+
if (ancestors && ancestors.length > 0) {
|
|
74
|
+
const parent = ancestors[0];
|
|
75
|
+
if (typeof parent === "string" && parent.length > 0) {
|
|
76
|
+
return parent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
36
83
|
configureActiveOrigin() {
|
|
37
84
|
if (typeof window === "undefined") {
|
|
38
85
|
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
39
86
|
return;
|
|
40
87
|
}
|
|
41
88
|
try {
|
|
89
|
+
const ancestorOrigin = this.readAncestorOrigin();
|
|
42
90
|
const urlParams = new URLSearchParams(window.location.search);
|
|
43
91
|
const hostOverride = urlParams.get("lc_host_override");
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
92
|
+
if (ancestorOrigin && this.isOriginInWhitelist(ancestorOrigin)) {
|
|
93
|
+
if (hostOverride && hostOverride !== ancestorOrigin) {
|
|
46
94
|
console.warn(
|
|
47
|
-
"[LearnCard SDK] lc_host_override
|
|
48
|
-
hostOverride,
|
|
49
|
-
"Allowed:",
|
|
50
|
-
this.hostOrigins
|
|
95
|
+
"[LearnCard SDK] lc_host_override does not match the real parent origin; preferring parent.",
|
|
96
|
+
{ override: hostOverride, parent: ancestorOrigin }
|
|
51
97
|
);
|
|
52
|
-
|
|
53
|
-
|
|
98
|
+
}
|
|
99
|
+
this.activeHostOrigin = ancestorOrigin;
|
|
100
|
+
this.persistOverride(ancestorOrigin);
|
|
101
|
+
console.log("[LearnCard SDK] Using parent origin:", ancestorOrigin);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (hostOverride) {
|
|
105
|
+
if (this.isOriginInWhitelist(hostOverride)) {
|
|
54
106
|
this.activeHostOrigin = hostOverride;
|
|
107
|
+
this.persistOverride(hostOverride);
|
|
55
108
|
console.log("[LearnCard SDK] Using lc_host_override:", hostOverride);
|
|
109
|
+
return;
|
|
56
110
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
111
|
+
console.warn(
|
|
112
|
+
"[LearnCard SDK] lc_host_override value is not in the configured whitelist:",
|
|
113
|
+
hostOverride,
|
|
114
|
+
"Allowed:",
|
|
115
|
+
this.hostOrigins
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
let storedOverride = null;
|
|
119
|
+
try {
|
|
120
|
+
storedOverride = sessionStorage.getItem(_PartnerConnect.SESSION_STORAGE_KEY);
|
|
121
|
+
} catch {
|
|
60
122
|
}
|
|
123
|
+
if (storedOverride && this.isOriginInWhitelist(storedOverride)) {
|
|
124
|
+
this.activeHostOrigin = storedOverride;
|
|
125
|
+
console.log("[LearnCard SDK] Using stored lc_host_override:", storedOverride);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
129
|
+
console.log("[LearnCard SDK] Using configured origin:", this.activeHostOrigin);
|
|
61
130
|
} catch (error) {
|
|
62
131
|
console.error("[LearnCard SDK] Error configuring active origin:", error);
|
|
63
132
|
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
64
133
|
}
|
|
65
134
|
}
|
|
135
|
+
persistOverride(origin) {
|
|
136
|
+
try {
|
|
137
|
+
sessionStorage.setItem(_PartnerConnect.SESSION_STORAGE_KEY, origin);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
66
141
|
isOriginNativeApp(origin) {
|
|
67
142
|
return origin.startsWith("capacitor://") || origin.startsWith("ionic://") || origin.startsWith("https://localhost") || origin.startsWith("http://localhost") || origin.startsWith("http://127.0.0.1");
|
|
68
143
|
}
|
|
69
144
|
/**
|
|
70
|
-
* Check
|
|
145
|
+
* Check whether a candidate origin matches a configured whitelist entry.
|
|
146
|
+
*
|
|
147
|
+
* Supports exact matches and wildcard patterns. A wildcard entry has the
|
|
148
|
+
* form `<protocol>://*.<domain>` and matches any origin with the same
|
|
149
|
+
* protocol, same port, and a host ending in `.<domain>` with at least
|
|
150
|
+
* one non-empty DNS label in place of the `*`.
|
|
151
|
+
*
|
|
152
|
+
* Examples with pattern `https://*.learncard.app`:
|
|
153
|
+
* - `https://staging.learncard.app` → match
|
|
154
|
+
* - `https://pr-1.preview.learncard.app` → match
|
|
155
|
+
* - `https://learncard.app` → no match (no subdomain)
|
|
156
|
+
* - `http://staging.learncard.app` → no match (protocol mismatch)
|
|
157
|
+
* - `https://learncard.app.attacker.com` → no match (suffix mismatch)
|
|
158
|
+
*
|
|
159
|
+
* Exposed as a public static so it can be unit-tested directly without
|
|
160
|
+
* standing up a full SDK instance.
|
|
161
|
+
*/
|
|
162
|
+
static matchesOriginPattern(candidate, pattern) {
|
|
163
|
+
if (candidate === pattern) return true;
|
|
164
|
+
if (!pattern.includes("*")) return false;
|
|
165
|
+
let patternUrl;
|
|
166
|
+
let candidateUrl;
|
|
167
|
+
try {
|
|
168
|
+
patternUrl = new URL(
|
|
169
|
+
pattern.replace(
|
|
170
|
+
_PartnerConnect.WILDCARD_REGEX,
|
|
171
|
+
_PartnerConnect.WILDCARD_PLACEHOLDER
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
candidateUrl = new URL(candidate);
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (patternUrl.protocol !== candidateUrl.protocol) return false;
|
|
179
|
+
if (patternUrl.port !== candidateUrl.port) return false;
|
|
180
|
+
const patternHost = patternUrl.hostname;
|
|
181
|
+
const candidateHost = candidateUrl.hostname;
|
|
182
|
+
if (!patternHost.startsWith(_PartnerConnect.WILDCARD_LEADING_PREFIX)) return false;
|
|
183
|
+
const patternSuffix = patternHost.slice(_PartnerConnect.WILDCARD_LEADING_PREFIX.length);
|
|
184
|
+
if (patternSuffix.length === 0) return false;
|
|
185
|
+
if (patternSuffix.includes(_PartnerConnect.WILDCARD_PLACEHOLDER)) return false;
|
|
186
|
+
const required = "." + patternSuffix;
|
|
187
|
+
if (!candidateHost.endsWith(required)) return false;
|
|
188
|
+
const prefix = candidateHost.slice(0, candidateHost.length - required.length);
|
|
189
|
+
if (prefix.length === 0) return false;
|
|
190
|
+
if (prefix.startsWith(".") || prefix.endsWith(".")) return false;
|
|
191
|
+
if (prefix.split(".").some((label) => label.length === 0)) return false;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check if an origin is in the effective whitelist (exact origins +
|
|
196
|
+
* wildcard patterns + optional native-app origins).
|
|
71
197
|
*/
|
|
72
198
|
isOriginInWhitelist(origin) {
|
|
73
|
-
|
|
199
|
+
if (!origin) return false;
|
|
200
|
+
for (const entry of this.hostOrigins) {
|
|
201
|
+
if (_PartnerConnect.matchesOriginPattern(origin, entry)) return true;
|
|
202
|
+
}
|
|
203
|
+
if (this.allowNativeAppOrigins && this.isOriginNativeApp(origin)) return true;
|
|
204
|
+
return false;
|
|
74
205
|
}
|
|
75
206
|
/**
|
|
76
207
|
* Check if an event origin is valid against the active host origin
|
|
@@ -109,7 +240,12 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
109
240
|
pending.resolve(data.data);
|
|
110
241
|
} else if (data.type === "ERROR") {
|
|
111
242
|
pending.reject(
|
|
112
|
-
|
|
243
|
+
PartnerConnectError.from(
|
|
244
|
+
data.error || {
|
|
245
|
+
code: "UNKNOWN_ERROR",
|
|
246
|
+
message: "An unknown error occurred"
|
|
247
|
+
}
|
|
248
|
+
)
|
|
113
249
|
);
|
|
114
250
|
}
|
|
115
251
|
};
|
|
@@ -127,20 +263,21 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
127
263
|
*/
|
|
128
264
|
sendMessage(action, payload) {
|
|
129
265
|
if (!this.isInitialized) {
|
|
130
|
-
return Promise.reject(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
266
|
+
return Promise.reject(
|
|
267
|
+
new PartnerConnectError("SDK_NOT_INITIALIZED", "SDK is not initialized")
|
|
268
|
+
);
|
|
134
269
|
}
|
|
135
270
|
return new Promise((resolve, reject) => {
|
|
136
271
|
const requestId = this.generateRequestId(action);
|
|
137
272
|
const timeoutId = setTimeout(() => {
|
|
138
273
|
if (this.pendingRequests.has(requestId)) {
|
|
139
274
|
this.pendingRequests.delete(requestId);
|
|
140
|
-
reject(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
275
|
+
reject(
|
|
276
|
+
new PartnerConnectError(
|
|
277
|
+
"LC_TIMEOUT",
|
|
278
|
+
`Request ${action} timed out after ${this.requestTimeout}ms`
|
|
279
|
+
)
|
|
280
|
+
);
|
|
144
281
|
}
|
|
145
282
|
}, this.requestTimeout);
|
|
146
283
|
this.pendingRequests.set(requestId, {
|
|
@@ -358,22 +495,27 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
358
495
|
/**
|
|
359
496
|
* Request user consent for permissions
|
|
360
497
|
*
|
|
361
|
-
* @param contractUri - URI of the consent contract
|
|
498
|
+
* @param contractUri - URI of the consent contract (optional for App Store apps with configured contracts)
|
|
499
|
+
* @param options - Additional options including redirect behavior
|
|
362
500
|
* @returns Promise resolving to consent response
|
|
363
501
|
*
|
|
364
502
|
* @example
|
|
365
503
|
* ```typescript
|
|
366
|
-
* //
|
|
504
|
+
* // With explicit contract URI (for external/non-app store integrations)
|
|
367
505
|
* const response = await learnCard.requestConsent('lc:network:network.learncard.com/trpc:contract:abc123');
|
|
368
506
|
* if (response.granted) {
|
|
369
507
|
* console.log('User granted consent');
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
508
|
+
* }
|
|
509
|
+
*
|
|
510
|
+
* // Without contract URI (uses app's configured contract from integration)
|
|
511
|
+
* // This works for App Store apps that have configured a contract in their integration
|
|
512
|
+
* const response = await learnCard.requestConsent();
|
|
513
|
+
* if (response.granted) {
|
|
514
|
+
* console.log('User granted consent using listing contract');
|
|
373
515
|
* }
|
|
374
516
|
*
|
|
375
517
|
* // With redirect - redirects to contract's redirectUrl with VP in URL params
|
|
376
|
-
* const response = await learnCard.requestConsent(
|
|
518
|
+
* const response = await learnCard.requestConsent(undefined, { redirect: true });
|
|
377
519
|
* ```
|
|
378
520
|
*/
|
|
379
521
|
requestConsent(contractUri, options = {}) {
|
|
@@ -408,6 +550,45 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
408
550
|
draftRecipients: draftRecipients || []
|
|
409
551
|
});
|
|
410
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Request comprehensive learner context for AI tutoring systems.
|
|
555
|
+
*
|
|
556
|
+
* This method retrieves the user's credentials and personal data,
|
|
557
|
+
* then formats them into an LLM-ready prompt that can be injected directly into
|
|
558
|
+
* an AI system prompt.
|
|
559
|
+
*
|
|
560
|
+
* @param options - Configuration options for what data to include and how to format it
|
|
561
|
+
* @returns Promise resolving to learner context with prompt and optional raw data
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```typescript
|
|
565
|
+
* // Get LLM-ready prompt with credentials and personal data
|
|
566
|
+
* const context = await learnCard.requestLearnerContext({
|
|
567
|
+
* includeCredentials: true,
|
|
568
|
+
* includePersonalData: true,
|
|
569
|
+
* format: 'prompt',
|
|
570
|
+
* instructions: 'Focus on technical skills and certifications',
|
|
571
|
+
* detailLevel: 'expanded'
|
|
572
|
+
* });
|
|
573
|
+
*
|
|
574
|
+
* // Use in AI system prompt
|
|
575
|
+
* const systemPrompt = `You are a helpful tutor. ${context.prompt}`;
|
|
576
|
+
*
|
|
577
|
+
* // Access structured data if needed
|
|
578
|
+
* console.log('User DID:', context.did);
|
|
579
|
+
* console.log('Credentials count:', context.raw?.credentials.length);
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
requestLearnerContext(options) {
|
|
583
|
+
var _a, _b, _c, _d;
|
|
584
|
+
return this.sendMessage("REQUEST_LEARNER_CONTEXT", {
|
|
585
|
+
includeCredentials: (_a = options == null ? void 0 : options.includeCredentials) != null ? _a : true,
|
|
586
|
+
includePersonalData: (_b = options == null ? void 0 : options.includePersonalData) != null ? _b : false,
|
|
587
|
+
format: (_c = options == null ? void 0 : options.format) != null ? _c : "prompt",
|
|
588
|
+
instructions: options == null ? void 0 : options.instructions,
|
|
589
|
+
detailLevel: (_d = options == null ? void 0 : options.detailLevel) != null ? _d : "compact"
|
|
590
|
+
});
|
|
591
|
+
}
|
|
411
592
|
/**
|
|
412
593
|
* Send a generic event to be processed by the brain service on behalf of this app.
|
|
413
594
|
* This is used for backend-like operations such as issuing credentials.
|
|
@@ -432,6 +613,62 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
432
613
|
sendAppEvent(event) {
|
|
433
614
|
return this.sendMessage("APP_EVENT", event);
|
|
434
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Create and send an AI Session credential to the user.
|
|
618
|
+
*
|
|
619
|
+
* This method manages the AI Topic → AI Session hierarchy:
|
|
620
|
+
* - Ensures an AI Topic exists for this app (creates one if needed)
|
|
621
|
+
* - Creates a new AI Session as a child of the topic
|
|
622
|
+
* - The topic appears in the user's AI Sessions page with the app's name
|
|
623
|
+
* - All sessions from this app are organized under that topic
|
|
624
|
+
*
|
|
625
|
+
* @param input - Session details including title and optional metadata
|
|
626
|
+
* @returns Promise resolving to topic and session URIs
|
|
627
|
+
*/
|
|
628
|
+
sendAiSessionCredential(input) {
|
|
629
|
+
return this.sendAppEvent({
|
|
630
|
+
type: "send-ai-session-credential",
|
|
631
|
+
...input
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Send a notification to the current user from this app.
|
|
636
|
+
* The notification appears in the user's LearnCard notification inbox.
|
|
637
|
+
*/
|
|
638
|
+
sendNotification(input) {
|
|
639
|
+
return this.sendAppEvent({
|
|
640
|
+
type: "send-notification",
|
|
641
|
+
...input
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Increment or decrement an app-scoped counter for the current user.
|
|
646
|
+
*/
|
|
647
|
+
incrementCounter(key, amount) {
|
|
648
|
+
return this.sendAppEvent({
|
|
649
|
+
type: "increment-counter",
|
|
650
|
+
key,
|
|
651
|
+
amount
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Read the current value of an app-scoped counter for the current user.
|
|
656
|
+
*/
|
|
657
|
+
getCounter(key) {
|
|
658
|
+
return this.sendAppEvent({
|
|
659
|
+
type: "get-counter",
|
|
660
|
+
key
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Read multiple app-scoped counters at once for the current user.
|
|
665
|
+
*/
|
|
666
|
+
getCounters(keys) {
|
|
667
|
+
return this.sendAppEvent({
|
|
668
|
+
type: "get-counters",
|
|
669
|
+
...keys ? { keys } : {}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
435
672
|
/**
|
|
436
673
|
* Clean up the SDK and remove event listeners
|
|
437
674
|
*/
|
|
@@ -442,10 +679,12 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
442
679
|
}
|
|
443
680
|
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
444
681
|
clearTimeout(pending.timeoutId);
|
|
445
|
-
pending.reject(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
682
|
+
pending.reject(
|
|
683
|
+
new PartnerConnectError(
|
|
684
|
+
"SDK_DESTROYED",
|
|
685
|
+
"SDK was destroyed before request completed"
|
|
686
|
+
)
|
|
687
|
+
);
|
|
449
688
|
}
|
|
450
689
|
this.pendingRequests.clear();
|
|
451
690
|
this.isInitialized = false;
|
|
@@ -453,12 +692,57 @@ const _PartnerConnect = class _PartnerConnect {
|
|
|
453
692
|
};
|
|
454
693
|
/** Default host origin (security anchor) */
|
|
455
694
|
__publicField(_PartnerConnect, "DEFAULT_HOST_ORIGIN", "https://learncard.app");
|
|
695
|
+
/**
|
|
696
|
+
* Built-in list of LearnCard-managed tenant origins.
|
|
697
|
+
*
|
|
698
|
+
* These are merged with the partner app's configured `hostOrigin` whitelist
|
|
699
|
+
* unless `disableDefaultTenants: true` is passed. This lets a partner app
|
|
700
|
+
* run inside any current or future LearnCard tenant (staging, preview,
|
|
701
|
+
* VetPass, etc.) without needing a re-deploy each time a new tenant is
|
|
702
|
+
* onboarded.
|
|
703
|
+
*
|
|
704
|
+
* Patterns follow the same rules as user-supplied `hostOrigin` entries:
|
|
705
|
+
* `*` is a wildcard for one or more DNS labels in the host portion.
|
|
706
|
+
*/
|
|
707
|
+
__publicField(_PartnerConnect, "DEFAULT_TRUSTED_TENANTS", [
|
|
708
|
+
"https://learncard.app",
|
|
709
|
+
"https://*.learncard.app",
|
|
710
|
+
"https://*.learncard.ai",
|
|
711
|
+
"https://vetpass.app",
|
|
712
|
+
"https://*.vetpass.app"
|
|
713
|
+
]);
|
|
714
|
+
/**
|
|
715
|
+
* Configure the active host origin using the following hierarchy:
|
|
716
|
+
* 1. `window.location.ancestorOrigins[0]` (when supported) — the browser's
|
|
717
|
+
* view of who our parent frame is. Cannot be forged by a malicious
|
|
718
|
+
* `lc_host_override` query param and therefore takes precedence.
|
|
719
|
+
* 2. `?lc_host_override=<origin>` query param (for staging / cross-tenant).
|
|
720
|
+
* 3. `sessionStorage` value saved from a previously-validated override.
|
|
721
|
+
* 4. First configured origin.
|
|
722
|
+
* 5. `DEFAULT_HOST_ORIGIN`.
|
|
723
|
+
*
|
|
724
|
+
* When a valid override is found in the query parameter, it is persisted
|
|
725
|
+
* to sessionStorage so subsequent in-iframe navigations in the same tab
|
|
726
|
+
* continue to use the same active origin.
|
|
727
|
+
*/
|
|
728
|
+
__publicField(_PartnerConnect, "SESSION_STORAGE_KEY", "lc_host_override");
|
|
729
|
+
/**
|
|
730
|
+
* Internal placeholder substituted in for `*` so that `new URL(...)` can
|
|
731
|
+
* parse a wildcard pattern. Chosen to be a syntactically-valid DNS label
|
|
732
|
+
* that cannot collide with a real hostname.
|
|
733
|
+
*/
|
|
734
|
+
__publicField(_PartnerConnect, "WILDCARD_PLACEHOLDER", "__lc_wildcard__");
|
|
735
|
+
/** `*` (any number of occurrences) for replacement in the pattern. */
|
|
736
|
+
__publicField(_PartnerConnect, "WILDCARD_REGEX", /\*/g);
|
|
737
|
+
/** The required leading-label form a wildcard pattern must take. */
|
|
738
|
+
__publicField(_PartnerConnect, "WILDCARD_LEADING_PREFIX", `${_PartnerConnect.WILDCARD_PLACEHOLDER}.`);
|
|
456
739
|
let PartnerConnect = _PartnerConnect;
|
|
457
740
|
function createPartnerConnect(options) {
|
|
458
741
|
return new PartnerConnect(options);
|
|
459
742
|
}
|
|
460
743
|
|
|
461
744
|
exports.PartnerConnect = PartnerConnect;
|
|
745
|
+
exports.PartnerConnectError = PartnerConnectError;
|
|
462
746
|
exports.createPartnerConnect = createPartnerConnect;
|
|
463
747
|
exports["default"] = createPartnerConnect;
|
|
464
748
|
//# sourceMappingURL=partner-connect.js.map
|