@learncard/partner-connect 0.3.6 → 0.3.8
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 +48 -16
- package/dist/index.d.ts +45 -1
- package/dist/partner-connect.esm.js +74 -6
- package/dist/partner-connect.esm.js.map +1 -1
- package/dist/partner-connect.js +74 -6
- package/dist/partner-connect.js.map +1 -1
- package/dist/partner-connect.mjs +74 -6
- package/dist/partner-connect.mjs.map +1 -1
- package/package.json +59 -57
- package/src/index.ts +1013 -0
- package/src/origin-resolution.test.ts +374 -0
- package/src/pattern-matching.test.ts +134 -0
- package/src/types.ts +666 -0
- package/LICENSE +0 -21
package/src/types.ts
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LearnCard Partner Connect SDK - Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Re-export AppEvent types from shared types package
|
|
6
|
+
export type {
|
|
7
|
+
AppEvent,
|
|
8
|
+
SendCredentialEvent,
|
|
9
|
+
CheckCredentialEvent,
|
|
10
|
+
AppEventResponse,
|
|
11
|
+
} from '@learncard/types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Configuration options for initializing the SDK
|
|
15
|
+
*/
|
|
16
|
+
export interface PartnerConnectOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The origin(s) of the LearnCard host.
|
|
19
|
+
*
|
|
20
|
+
* Each entry may be either an **exact origin** (`https://learncard.app`) or a
|
|
21
|
+
* **wildcard pattern** where `*` stands in for a single DNS label portion in
|
|
22
|
+
* the host portion of the origin (e.g. `https://*.learncard.app`,
|
|
23
|
+
* `https://*.vetpass.app`). Wildcards are **only** allowed in the host and
|
|
24
|
+
* only as label(s); the protocol and port must always match exactly.
|
|
25
|
+
*
|
|
26
|
+
* Wildcard patterns match any non-empty chain of labels. So
|
|
27
|
+
* `https://*.learncard.app` matches both `https://staging.learncard.app` and
|
|
28
|
+
* `https://pr-123.preview.learncard.app`, but **not** `https://learncard.app`
|
|
29
|
+
* itself (include the bare origin explicitly if you need it) and **not**
|
|
30
|
+
* `https://evil.learncard.app.attacker.com` (the suffix must match).
|
|
31
|
+
*
|
|
32
|
+
* **Origin Configuration Hierarchy at runtime:**
|
|
33
|
+
* 1. `window.location.ancestorOrigins[0]` (when available) — the real parent
|
|
34
|
+
* origin as reported by the browser, validated against the effective
|
|
35
|
+
* whitelist. This source cannot be spoofed by a malicious query param.
|
|
36
|
+
* 2. `?lc_host_override=<origin>` query parameter — validated against the
|
|
37
|
+
* whitelist. Used by the LearnCard host to tell the SDK which origin it
|
|
38
|
+
* is loading from.
|
|
39
|
+
* 3. `sessionStorage['lc_host_override']` — a previously-validated override
|
|
40
|
+
* persisted across in-iframe navigation.
|
|
41
|
+
* 4. First value in the configured `hostOrigin` array / single string.
|
|
42
|
+
* 5. `PartnerConnect.DEFAULT_HOST_ORIGIN` (`https://learncard.app`).
|
|
43
|
+
*
|
|
44
|
+
* The partner app's configured whitelist is combined with a small built-in
|
|
45
|
+
* list of LearnCard tenant domains (see `disableDefaultTenants` to opt out).
|
|
46
|
+
* This lets a partner app work out-of-the-box inside any current or future
|
|
47
|
+
* `*.learncard.app`, `*.learncard.ai`, or `*.vetpass.app` tenant without a
|
|
48
|
+
* re-deploy.
|
|
49
|
+
*
|
|
50
|
+
* **Examples:**
|
|
51
|
+
*
|
|
52
|
+
* Single origin (production only):
|
|
53
|
+
* ```typescript
|
|
54
|
+
* hostOrigin: 'https://learncard.app'
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Wildcard whitelist (covers staging + preview):
|
|
58
|
+
* ```typescript
|
|
59
|
+
* hostOrigin: ['https://learncard.app', 'https://*.learncard.app']
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* Custom tenant alongside the built-in LearnCard defaults:
|
|
63
|
+
* ```typescript
|
|
64
|
+
* hostOrigin: ['https://partner.example.com']
|
|
65
|
+
* // learncard.app / *.learncard.app / *.learncard.ai / vetpass.app / *.vetpass.app
|
|
66
|
+
* // are ALSO trusted because disableDefaultTenants is false.
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @default 'https://learncard.app'
|
|
70
|
+
*/
|
|
71
|
+
hostOrigin?: string | string[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Opt out of the built-in LearnCard tenant whitelist.
|
|
75
|
+
*
|
|
76
|
+
* By default, the SDK merges `hostOrigin` with a curated list of LearnCard
|
|
77
|
+
* and tenant domains (see `PartnerConnect.DEFAULT_TRUSTED_TENANTS`) so that
|
|
78
|
+
* partner apps work on any LearnCard-managed tenant without reconfiguration.
|
|
79
|
+
*
|
|
80
|
+
* Set to `true` if you want the partner app to **only** trust the origins
|
|
81
|
+
* you pass in `hostOrigin`.
|
|
82
|
+
*
|
|
83
|
+
* @default false
|
|
84
|
+
*/
|
|
85
|
+
disableDefaultTenants?: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether to allow native app origins (default: true)
|
|
89
|
+
*
|
|
90
|
+
* @default true
|
|
91
|
+
*/
|
|
92
|
+
allowNativeAppOrigins?: boolean;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Protocol identifier (default: 'LEARNCARD_V1')
|
|
96
|
+
*/
|
|
97
|
+
protocol?: string;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Request timeout in milliseconds (default: 30000)
|
|
101
|
+
*/
|
|
102
|
+
requestTimeout?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Identity information returned from REQUEST_IDENTITY
|
|
107
|
+
*/
|
|
108
|
+
export interface IdentityResponse {
|
|
109
|
+
token: string;
|
|
110
|
+
user: {
|
|
111
|
+
did: string;
|
|
112
|
+
[key: string]: unknown;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Response from SEND_CREDENTIAL action (raw credential)
|
|
118
|
+
*/
|
|
119
|
+
export interface SendCredentialResponse {
|
|
120
|
+
credentialId: string;
|
|
121
|
+
[key: string]: unknown;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Template-based credential input for sendCredential
|
|
126
|
+
* Uses a pre-configured boost template to issue a credential
|
|
127
|
+
*/
|
|
128
|
+
export interface TemplateCredentialInput {
|
|
129
|
+
/** Alias of the boost template configured for this app */
|
|
130
|
+
templateAlias: string;
|
|
131
|
+
|
|
132
|
+
/** Optional template data for Mustache-style variable substitution */
|
|
133
|
+
templateData?: Record<string, unknown>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* If true, the host will return an existing credential (if present) instead of issuing a duplicate.
|
|
137
|
+
*/
|
|
138
|
+
preventDuplicateClaim?: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Response from template-based credential issuance
|
|
143
|
+
*/
|
|
144
|
+
export interface TemplateCredentialResponse {
|
|
145
|
+
/** URI of the issued credential */
|
|
146
|
+
credentialUri: string;
|
|
147
|
+
|
|
148
|
+
/** URI of the boost template used */
|
|
149
|
+
boostUri: string;
|
|
150
|
+
|
|
151
|
+
/** Whether the credential was already claimed (when preventDuplicateClaim is true) */
|
|
152
|
+
alreadyClaimed?: boolean;
|
|
153
|
+
|
|
154
|
+
/** Whether the user has the credential (when preventDuplicateClaim is true) */
|
|
155
|
+
hasCredential?: boolean;
|
|
156
|
+
|
|
157
|
+
/** The status of the credential (when preventDuplicateClaim is true) */
|
|
158
|
+
status?: 'pending' | 'claimed' | 'revoked';
|
|
159
|
+
|
|
160
|
+
/** The date the credential was received (when preventDuplicateClaim is true) */
|
|
161
|
+
receivedDate?: string;
|
|
162
|
+
|
|
163
|
+
[key: string]: unknown;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Verifiable Presentation Request Query types
|
|
168
|
+
*/
|
|
169
|
+
export type VPRQuery =
|
|
170
|
+
| {
|
|
171
|
+
type: 'QueryByTitle';
|
|
172
|
+
credentialQuery: {
|
|
173
|
+
reason?: string;
|
|
174
|
+
title: string;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
| {
|
|
178
|
+
type: 'QueryByExample';
|
|
179
|
+
credentialQuery: unknown;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Verifiable Presentation Request structure
|
|
184
|
+
*/
|
|
185
|
+
export interface VerifiablePresentationRequest {
|
|
186
|
+
query: VPRQuery[];
|
|
187
|
+
challenge: string;
|
|
188
|
+
domain: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Response from ASK_CREDENTIAL_SEARCH action
|
|
193
|
+
*/
|
|
194
|
+
export interface CredentialSearchResponse {
|
|
195
|
+
verifiablePresentation?: {
|
|
196
|
+
verifiableCredential: unknown[];
|
|
197
|
+
[key: string]: unknown;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Response from ASK_CREDENTIAL_SPECIFIC action
|
|
203
|
+
*/
|
|
204
|
+
export interface CredentialSpecificResponse {
|
|
205
|
+
credential?: unknown;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Options for REQUEST_CONSENT action
|
|
210
|
+
*/
|
|
211
|
+
export interface RequestConsentOptions {
|
|
212
|
+
/**
|
|
213
|
+
* If true, redirect to contract's redirectUrl with VP after consent.
|
|
214
|
+
* Default: false
|
|
215
|
+
*/
|
|
216
|
+
redirect?: boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Payload for REQUEST_CONSENT action
|
|
221
|
+
*/
|
|
222
|
+
export interface RequestConsentPayload {
|
|
223
|
+
/**
|
|
224
|
+
* URI of the consent contract. If not provided, the system will attempt
|
|
225
|
+
* to use the contract configured for the current app listing.
|
|
226
|
+
*/
|
|
227
|
+
contractUri?: string;
|
|
228
|
+
redirect?: boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Response from REQUEST_CONSENT action
|
|
233
|
+
*/
|
|
234
|
+
export interface ConsentResponse {
|
|
235
|
+
granted: boolean;
|
|
236
|
+
[key: string]: unknown;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Response from INITIATE_TEMPLATE_ISSUE action
|
|
241
|
+
*/
|
|
242
|
+
export interface TemplateIssueResponse {
|
|
243
|
+
issued: boolean;
|
|
244
|
+
[key: string]: unknown;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// AppEvent, SendCredentialEvent, CheckCredentialEvent, and AppEventResponse are re-exported from @learncard/types above
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Input used to check if the authenticated user already has a credential for a boost template
|
|
251
|
+
*/
|
|
252
|
+
export type CheckCredentialInput = { templateAlias: string } | { boostUri: string };
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Response from checkUserHasCredential
|
|
256
|
+
*/
|
|
257
|
+
export interface CheckCredentialResponse {
|
|
258
|
+
hasCredential: boolean;
|
|
259
|
+
credentialUri?: string;
|
|
260
|
+
receivedDate?: string;
|
|
261
|
+
status?: 'pending' | 'claimed' | 'revoked';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Input for checking template issuance status to a specific recipient.
|
|
266
|
+
* The recipient can be either a DID (did:web:...) or a profileId.
|
|
267
|
+
*/
|
|
268
|
+
export type CheckIssuanceStatusInput =
|
|
269
|
+
| { templateAlias: string; recipient: string }
|
|
270
|
+
| { boostUri: string; recipient: string };
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Response from getTemplateIssuanceStatus
|
|
274
|
+
*/
|
|
275
|
+
export interface TemplateIssuanceStatusResponse {
|
|
276
|
+
sent: boolean;
|
|
277
|
+
credentialUri?: string;
|
|
278
|
+
sentDate?: string;
|
|
279
|
+
claimedDate?: string;
|
|
280
|
+
status?: 'pending' | 'claimed' | 'revoked';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Input for getting template recipients list
|
|
285
|
+
*/
|
|
286
|
+
export type GetTemplateRecipientsInput =
|
|
287
|
+
| { templateAlias: string; limit?: number; cursor?: string }
|
|
288
|
+
| { boostUri: string; limit?: number; cursor?: string };
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Individual recipient record in the template recipients response
|
|
292
|
+
*/
|
|
293
|
+
export interface TemplateRecipientRecord {
|
|
294
|
+
recipientProfileId: string;
|
|
295
|
+
recipientDisplayName?: string;
|
|
296
|
+
sentDate: string;
|
|
297
|
+
claimedDate?: string;
|
|
298
|
+
credentialUri?: string;
|
|
299
|
+
status: 'pending' | 'claimed' | 'revoked';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Response from getTemplateRecipients
|
|
304
|
+
*/
|
|
305
|
+
export interface TemplateRecipientsResponse {
|
|
306
|
+
records: TemplateRecipientRecord[];
|
|
307
|
+
hasMore: boolean;
|
|
308
|
+
cursor?: string;
|
|
309
|
+
total?: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Options for REQUEST_LEARNER_CONTEXT action
|
|
314
|
+
*/
|
|
315
|
+
export interface RequestLearnerContextOptions {
|
|
316
|
+
/**
|
|
317
|
+
* Whether to include credentials in the context
|
|
318
|
+
* @default true
|
|
319
|
+
*/
|
|
320
|
+
includeCredentials?: boolean;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Whether to include personal data (name, bio, etc.) in the context
|
|
324
|
+
* @default false
|
|
325
|
+
*/
|
|
326
|
+
includePersonalData?: boolean;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Format of the response
|
|
330
|
+
* - 'prompt': Returns LLM-ready formatted text
|
|
331
|
+
* - 'structured': Returns structured data object
|
|
332
|
+
* @default 'prompt'
|
|
333
|
+
*/
|
|
334
|
+
format?: 'prompt' | 'structured';
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Optional instructions to guide the LLM prompt generation
|
|
338
|
+
*/
|
|
339
|
+
instructions?: string;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Level of detail in the generated prompt
|
|
343
|
+
* @default 'compact'
|
|
344
|
+
*/
|
|
345
|
+
detailLevel?: 'compact' | 'expanded';
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Wait for LearnCard to finish background ConsentFlow data sync before returning context.
|
|
349
|
+
* Apps that need a complete learner snapshot should set this to true.
|
|
350
|
+
* @default false
|
|
351
|
+
*/
|
|
352
|
+
waitForSync?: boolean;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Raw data included in learner context response (when format is 'structured')
|
|
357
|
+
*/
|
|
358
|
+
export interface LearnerContextRawData {
|
|
359
|
+
/** Array of Verifiable Credentials */
|
|
360
|
+
credentials: unknown[];
|
|
361
|
+
|
|
362
|
+
/** Personal data if requested and available (name, bio, etc.) */
|
|
363
|
+
personalData?: Record<string, unknown>;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Response from REQUEST_LEARNER_CONTEXT action
|
|
368
|
+
*/
|
|
369
|
+
export interface LearnerContextResponse {
|
|
370
|
+
/** Whether the response used immediately available data or waited for a complete sync */
|
|
371
|
+
status?: 'ready' | 'syncing';
|
|
372
|
+
|
|
373
|
+
/** Current sync progress when available */
|
|
374
|
+
progress?: SyncProgress;
|
|
375
|
+
|
|
376
|
+
/** LLM-ready formatted prompt text */
|
|
377
|
+
prompt: string;
|
|
378
|
+
|
|
379
|
+
/** Raw structured data (only included when format is 'structured') */
|
|
380
|
+
raw?: LearnerContextRawData;
|
|
381
|
+
|
|
382
|
+
/** User's DID */
|
|
383
|
+
did: string;
|
|
384
|
+
|
|
385
|
+
/** User's display name if available */
|
|
386
|
+
displayName?: string;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface SyncProgress {
|
|
390
|
+
totalCredentials: number;
|
|
391
|
+
completedCredentials: number;
|
|
392
|
+
failedCredentials: number;
|
|
393
|
+
retryCount: number;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export interface SyncStatus {
|
|
397
|
+
status: 'ready' | 'syncing' | 'error';
|
|
398
|
+
progress: SyncProgress;
|
|
399
|
+
eta?: number;
|
|
400
|
+
lastError?: string;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Keywords for next steps in summary credential data
|
|
405
|
+
*/
|
|
406
|
+
export interface SummaryCredentialKeyword {
|
|
407
|
+
occupations: string[] | null;
|
|
408
|
+
careers: string[] | null;
|
|
409
|
+
jobs: string[] | null;
|
|
410
|
+
skills: string[] | null;
|
|
411
|
+
fieldOfStudy: string | null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Skill item in summary credential data
|
|
416
|
+
*/
|
|
417
|
+
export interface SummaryCredentialSkill {
|
|
418
|
+
title: string;
|
|
419
|
+
description: string;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Next step item in summary credential data
|
|
424
|
+
*
|
|
425
|
+
* `keywords` is optional. Omit it entirely (or pass `undefined`) when you do not
|
|
426
|
+
* have taxonomy data — you no longer need to pass a struct of `null` fields.
|
|
427
|
+
*/
|
|
428
|
+
export interface SummaryCredentialNextStep {
|
|
429
|
+
title: string;
|
|
430
|
+
description: string;
|
|
431
|
+
keywords?: SummaryCredentialKeyword;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Reflection item in summary credential data
|
|
436
|
+
*/
|
|
437
|
+
export interface SummaryCredentialReflection {
|
|
438
|
+
title: string;
|
|
439
|
+
description: string;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Summary data for an AI Session credential
|
|
444
|
+
* Contains structured information about what was learned
|
|
445
|
+
*/
|
|
446
|
+
export interface SummaryCredentialData {
|
|
447
|
+
/** Short, concise title for the learning session or credential */
|
|
448
|
+
title: string;
|
|
449
|
+
/** Comprehensive summary of what happened during the session */
|
|
450
|
+
summary: string;
|
|
451
|
+
/** Bullet points of key knowledge gained */
|
|
452
|
+
learned: string[];
|
|
453
|
+
/** Categorized skills learned during the session */
|
|
454
|
+
skills: SummaryCredentialSkill[];
|
|
455
|
+
/** Recommended follow-up activities or learning modules */
|
|
456
|
+
nextSteps: SummaryCredentialNextStep[];
|
|
457
|
+
/** Reflections on the learning experience */
|
|
458
|
+
reflections: SummaryCredentialReflection[];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Input for creating and sending an AI Session credential
|
|
463
|
+
*/
|
|
464
|
+
export interface SendAiSessionCredentialInput {
|
|
465
|
+
/** Title of this specific AI session */
|
|
466
|
+
sessionTitle: string;
|
|
467
|
+
/** Structured summary data about what was learned */
|
|
468
|
+
summaryData: SummaryCredentialData;
|
|
469
|
+
/** Optional metadata for the session */
|
|
470
|
+
metadata?: Record<string, unknown>;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Response from sending an AI Session credential
|
|
475
|
+
*/
|
|
476
|
+
export interface SendAiSessionCredentialResponse {
|
|
477
|
+
/** URI of the AI Topic (parent) boost */
|
|
478
|
+
topicUri: string;
|
|
479
|
+
/** URI of the topic credential, if a new topic was created */
|
|
480
|
+
topicCredentialUri?: string;
|
|
481
|
+
/** URI of the created AI Session credential */
|
|
482
|
+
sessionCredentialUri: string;
|
|
483
|
+
/** URI of the session boost (child of topic) */
|
|
484
|
+
sessionBoostUri: string;
|
|
485
|
+
/** Whether a new topic was created (true) or existing was used (false) */
|
|
486
|
+
isNewTopic: boolean;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Input for sending a notification to the current user from this app.
|
|
491
|
+
* The notification appears in the user's LearnCard notification inbox.
|
|
492
|
+
*/
|
|
493
|
+
export interface AppNotificationInput {
|
|
494
|
+
/** Notification title */
|
|
495
|
+
title?: string;
|
|
496
|
+
|
|
497
|
+
/** Notification body text */
|
|
498
|
+
body?: string;
|
|
499
|
+
|
|
500
|
+
/** Deep link path within the app (e.g. '/prizes') */
|
|
501
|
+
actionPath?: string;
|
|
502
|
+
|
|
503
|
+
/** Grouping category (e.g. 'reward', 'announcement', 'status') */
|
|
504
|
+
category?: string;
|
|
505
|
+
|
|
506
|
+
/** Notification priority */
|
|
507
|
+
priority?: 'normal' | 'high';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Response from sendNotification
|
|
512
|
+
*/
|
|
513
|
+
export interface AppNotificationResponse {
|
|
514
|
+
sent: boolean;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Response from incrementCounter
|
|
519
|
+
*/
|
|
520
|
+
export interface IncrementCounterResponse {
|
|
521
|
+
key: string;
|
|
522
|
+
previousValue: number;
|
|
523
|
+
newValue: number;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Response from getCounter
|
|
528
|
+
*/
|
|
529
|
+
export interface GetCounterResponse {
|
|
530
|
+
key: string;
|
|
531
|
+
value: number;
|
|
532
|
+
updatedAt: string | null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Response from getCounters
|
|
537
|
+
*/
|
|
538
|
+
export interface GetCountersResponse {
|
|
539
|
+
counters: GetCounterResponse[];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Error codes that can be returned by the LearnCard host
|
|
544
|
+
*/
|
|
545
|
+
export type ErrorCode =
|
|
546
|
+
| 'LC_TIMEOUT'
|
|
547
|
+
| 'LC_UNAUTHENTICATED'
|
|
548
|
+
| 'CREDENTIAL_NOT_FOUND'
|
|
549
|
+
| 'USER_REJECTED'
|
|
550
|
+
| 'UNAUTHORIZED'
|
|
551
|
+
| 'TEMPLATE_NOT_FOUND'
|
|
552
|
+
| 'BOOST_NOT_FOUND'
|
|
553
|
+
| 'INSUFFICIENT_PERMISSIONS'
|
|
554
|
+
| string;
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Error object returned when a request fails.
|
|
558
|
+
*
|
|
559
|
+
* Historically the SDK rejected with a plain `{ code, message }` object. As of
|
|
560
|
+
* v0.3.0 we reject with a {@link PartnerConnectError} instance instead, which
|
|
561
|
+
* still satisfies this interface (it has both `code` and `message` fields), so
|
|
562
|
+
* existing consumers that do `if (err.code === '...')` continue to work
|
|
563
|
+
* unchanged.
|
|
564
|
+
*/
|
|
565
|
+
export interface LearnCardError {
|
|
566
|
+
code: ErrorCode;
|
|
567
|
+
message: string;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Typed error class for all Partner Connect SDK rejections.
|
|
572
|
+
*
|
|
573
|
+
* Use `instanceof PartnerConnectError` to narrow caught errors and unlock
|
|
574
|
+
* exhaustive `switch` checks on `code`. Both `code` and `message` are present
|
|
575
|
+
* (so the legacy `LearnCardError` object shape is preserved), and `name` is
|
|
576
|
+
* always `'PartnerConnectError'`.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```typescript
|
|
580
|
+
* try {
|
|
581
|
+
* await learnCard.requestLearnerContext();
|
|
582
|
+
* } catch (err) {
|
|
583
|
+
* if (err instanceof PartnerConnectError) {
|
|
584
|
+
* switch (err.code) {
|
|
585
|
+
* case 'LC_UNAUTHENTICATED': showLogin(); break;
|
|
586
|
+
* case 'USER_REJECTED': showPrivacyNotice(); break;
|
|
587
|
+
* case 'UNAUTHORIZED': showPermissionsError(); break;
|
|
588
|
+
* default: console.error(err);
|
|
589
|
+
* }
|
|
590
|
+
* }
|
|
591
|
+
* }
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
export class PartnerConnectError extends Error implements LearnCardError {
|
|
595
|
+
public readonly code: ErrorCode;
|
|
596
|
+
|
|
597
|
+
constructor(code: ErrorCode, message: string) {
|
|
598
|
+
super(message);
|
|
599
|
+
this.name = 'PartnerConnectError';
|
|
600
|
+
this.code = code;
|
|
601
|
+
|
|
602
|
+
// Restore prototype chain when transpiled to ES5 targets.
|
|
603
|
+
Object.setPrototypeOf(this, PartnerConnectError.prototype);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Wrap any incoming `LearnCardError`-shaped value into a `PartnerConnectError`.
|
|
608
|
+
* Returns the value unchanged if it is already an instance.
|
|
609
|
+
*
|
|
610
|
+
* Used internally at every reject site so callers always receive a typed
|
|
611
|
+
* `PartnerConnectError`, regardless of whether the failure originated from
|
|
612
|
+
* the host (over postMessage), an SDK timeout, or `destroy()`.
|
|
613
|
+
*/
|
|
614
|
+
public static from(input: LearnCardError | unknown): PartnerConnectError {
|
|
615
|
+
if (input instanceof PartnerConnectError) return input;
|
|
616
|
+
|
|
617
|
+
if (
|
|
618
|
+
input &&
|
|
619
|
+
typeof input === 'object' &&
|
|
620
|
+
'code' in input &&
|
|
621
|
+
typeof (input as { code: unknown }).code === 'string'
|
|
622
|
+
) {
|
|
623
|
+
const candidate = input as { code: string; message?: unknown };
|
|
624
|
+
const message =
|
|
625
|
+
typeof candidate.message === 'string'
|
|
626
|
+
? candidate.message
|
|
627
|
+
: 'Partner Connect request failed';
|
|
628
|
+
return new PartnerConnectError(candidate.code as ErrorCode, message);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return new PartnerConnectError(
|
|
632
|
+
'UNKNOWN_ERROR',
|
|
633
|
+
input instanceof Error ? input.message : 'An unknown error occurred'
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Internal message structure sent via postMessage
|
|
640
|
+
*/
|
|
641
|
+
export interface PostMessageRequest {
|
|
642
|
+
protocol: string;
|
|
643
|
+
action: string;
|
|
644
|
+
requestId: string;
|
|
645
|
+
payload?: unknown;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Internal message structure received via postMessage
|
|
650
|
+
*/
|
|
651
|
+
export interface PostMessageResponse {
|
|
652
|
+
protocol: string;
|
|
653
|
+
requestId: string;
|
|
654
|
+
type: 'SUCCESS' | 'ERROR';
|
|
655
|
+
data?: unknown;
|
|
656
|
+
error?: LearnCardError;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Pending request tracking structure
|
|
661
|
+
*/
|
|
662
|
+
export interface PendingRequest {
|
|
663
|
+
resolve: (value: unknown) => void;
|
|
664
|
+
reject: (error: LearnCardError) => void;
|
|
665
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
666
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|