@learncard/partner-connect 0.2.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/LICENSE +21 -0
- package/README.md +534 -0
- package/dist/index.d.ts +383 -0
- package/dist/partner-connect.esm.js +324 -0
- package/dist/partner-connect.esm.js.map +1 -0
- package/dist/partner-connect.js +330 -0
- package/dist/partner-connect.js.map +1 -0
- package/package.json +45 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LearnCard Partner Connect SDK - Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for initializing the SDK
|
|
6
|
+
*/
|
|
7
|
+
interface PartnerConnectOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The origin(s) of the LearnCard host
|
|
10
|
+
*
|
|
11
|
+
* This can be a single string or an array of strings to serve as a whitelist for
|
|
12
|
+
* the `lc_host_override` query parameter.
|
|
13
|
+
*
|
|
14
|
+
* **Origin Configuration Hierarchy:**
|
|
15
|
+
* 1. **Hardcoded Default**: `https://learncard.app` (security anchor)
|
|
16
|
+
* 2. **Query Parameter Override**: `?lc_host_override=https://staging.learncard.app`
|
|
17
|
+
* - Checked against whitelist if `hostOrigin` is provided
|
|
18
|
+
* - Used for staging/testing environments
|
|
19
|
+
* 3. **Configured Origin**: First value in array or single string value
|
|
20
|
+
*
|
|
21
|
+
* **Security Model:**
|
|
22
|
+
* - The SDK enforces STRICT origin validation
|
|
23
|
+
* - Incoming messages must EXACTLY match the active host origin
|
|
24
|
+
* - Prevents origin spoofing: even if malicious query param is added,
|
|
25
|
+
* messages from unauthorized origins are rejected
|
|
26
|
+
*
|
|
27
|
+
* **Examples:**
|
|
28
|
+
*
|
|
29
|
+
* Single origin (production):
|
|
30
|
+
* ```typescript
|
|
31
|
+
* hostOrigin: 'https://learncard.app'
|
|
32
|
+
* // Uses: https://learncard.app
|
|
33
|
+
* // Override: ?lc_host_override=https://staging.learncard.app (not validated)
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Multiple origins (whitelist for staging):
|
|
37
|
+
* ```typescript
|
|
38
|
+
* hostOrigin: ['https://learncard.app', 'https://staging.learncard.app']
|
|
39
|
+
* // Default: https://learncard.app
|
|
40
|
+
* // Override: ?lc_host_override=https://staging.learncard.app (validated)
|
|
41
|
+
* // Invalid: ?lc_host_override=https://evil.com (rejected)
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @default 'https://learncard.app'
|
|
45
|
+
*/
|
|
46
|
+
hostOrigin?: string | string[];
|
|
47
|
+
/**
|
|
48
|
+
* Whether to allow native app origins (default: true)
|
|
49
|
+
*
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
allowNativeAppOrigins?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Protocol identifier (default: 'LEARNCARD_V1')
|
|
55
|
+
*/
|
|
56
|
+
protocol?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Request timeout in milliseconds (default: 30000)
|
|
59
|
+
*/
|
|
60
|
+
requestTimeout?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Identity information returned from REQUEST_IDENTITY
|
|
64
|
+
*/
|
|
65
|
+
interface IdentityResponse {
|
|
66
|
+
token: string;
|
|
67
|
+
user: {
|
|
68
|
+
did: string;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Response from SEND_CREDENTIAL action
|
|
74
|
+
*/
|
|
75
|
+
interface SendCredentialResponse {
|
|
76
|
+
credentialId: string;
|
|
77
|
+
[key: string]: unknown;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Verifiable Presentation Request Query types
|
|
81
|
+
*/
|
|
82
|
+
type VPRQuery = {
|
|
83
|
+
type: 'QueryByTitle';
|
|
84
|
+
credentialQuery: {
|
|
85
|
+
reason?: string;
|
|
86
|
+
title: string;
|
|
87
|
+
};
|
|
88
|
+
} | {
|
|
89
|
+
type: 'QueryByExample';
|
|
90
|
+
credentialQuery: unknown;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Verifiable Presentation Request structure
|
|
94
|
+
*/
|
|
95
|
+
interface VerifiablePresentationRequest {
|
|
96
|
+
query: VPRQuery[];
|
|
97
|
+
challenge: string;
|
|
98
|
+
domain: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Response from ASK_CREDENTIAL_SEARCH action
|
|
102
|
+
*/
|
|
103
|
+
interface CredentialSearchResponse {
|
|
104
|
+
verifiablePresentation?: {
|
|
105
|
+
verifiableCredential: unknown[];
|
|
106
|
+
[key: string]: unknown;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Response from ASK_CREDENTIAL_SPECIFIC action
|
|
111
|
+
*/
|
|
112
|
+
interface CredentialSpecificResponse {
|
|
113
|
+
credential?: unknown;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Response from REQUEST_CONSENT action
|
|
117
|
+
*/
|
|
118
|
+
interface ConsentResponse {
|
|
119
|
+
granted: boolean;
|
|
120
|
+
[key: string]: unknown;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Response from INITIATE_TEMPLATE_ISSUE action
|
|
124
|
+
*/
|
|
125
|
+
interface TemplateIssueResponse {
|
|
126
|
+
issued: boolean;
|
|
127
|
+
[key: string]: unknown;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Error codes that can be returned by the LearnCard host
|
|
131
|
+
*/
|
|
132
|
+
type ErrorCode = 'LC_TIMEOUT' | 'LC_UNAUTHENTICATED' | 'CREDENTIAL_NOT_FOUND' | 'USER_REJECTED' | 'UNAUTHORIZED' | 'TEMPLATE_NOT_FOUND' | string;
|
|
133
|
+
/**
|
|
134
|
+
* Error object returned when a request fails
|
|
135
|
+
*/
|
|
136
|
+
interface LearnCardError {
|
|
137
|
+
code: ErrorCode;
|
|
138
|
+
message: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Internal message structure sent via postMessage
|
|
142
|
+
*/
|
|
143
|
+
interface PostMessageRequest {
|
|
144
|
+
protocol: string;
|
|
145
|
+
action: string;
|
|
146
|
+
requestId: string;
|
|
147
|
+
payload?: unknown;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Internal message structure received via postMessage
|
|
151
|
+
*/
|
|
152
|
+
interface PostMessageResponse {
|
|
153
|
+
protocol: string;
|
|
154
|
+
requestId: string;
|
|
155
|
+
type: 'SUCCESS' | 'ERROR';
|
|
156
|
+
data?: unknown;
|
|
157
|
+
error?: LearnCardError;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Pending request tracking structure
|
|
161
|
+
*/
|
|
162
|
+
interface PendingRequest {
|
|
163
|
+
resolve: (value: unknown) => void;
|
|
164
|
+
reject: (error: LearnCardError) => void;
|
|
165
|
+
timeoutId: NodeJS.Timeout;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* LearnCard Partner Connect SDK
|
|
170
|
+
*
|
|
171
|
+
* A Promise-based JavaScript utility for managing cross-origin message communication
|
|
172
|
+
* between partner apps and the LearnCard host application.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* import { createPartnerConnect } from '@learncard/partner-connect';
|
|
177
|
+
*
|
|
178
|
+
* const learnCard = createPartnerConnect({
|
|
179
|
+
* hostOrigin: 'https://learncard.app'
|
|
180
|
+
* });
|
|
181
|
+
*
|
|
182
|
+
* // Request user identity (SSO)
|
|
183
|
+
* const identity = await learnCard.requestIdentity();
|
|
184
|
+
* console.log('User DID:', identity.user.did);
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* LearnCard Partner Connect SDK class
|
|
190
|
+
*/
|
|
191
|
+
declare class PartnerConnect {
|
|
192
|
+
/** Default host origin (security anchor) */
|
|
193
|
+
static readonly DEFAULT_HOST_ORIGIN = "https://learncard.app";
|
|
194
|
+
private hostOrigins;
|
|
195
|
+
private activeHostOrigin;
|
|
196
|
+
private allowNativeAppOrigins;
|
|
197
|
+
private protocol;
|
|
198
|
+
private requestTimeout;
|
|
199
|
+
private pendingRequests;
|
|
200
|
+
private messageListener;
|
|
201
|
+
private isInitialized;
|
|
202
|
+
constructor(options?: PartnerConnectOptions);
|
|
203
|
+
/**
|
|
204
|
+
* Configure the active host origin using the following hierarchy:
|
|
205
|
+
* 1. Check for `lc_host_override` query parameter (for staging/testing)
|
|
206
|
+
* 2. Fall back to first configured origin
|
|
207
|
+
* 3. Fall back to DEFAULT_HOST_ORIGIN
|
|
208
|
+
*
|
|
209
|
+
* This origin will be used for all outgoing messages and incoming message validation.
|
|
210
|
+
*/
|
|
211
|
+
private configureActiveOrigin;
|
|
212
|
+
private isOriginNativeApp;
|
|
213
|
+
/**
|
|
214
|
+
* Check if an origin is in the configured whitelist
|
|
215
|
+
*/
|
|
216
|
+
private isOriginInWhitelist;
|
|
217
|
+
/**
|
|
218
|
+
* Check if an event origin is valid against the active host origin
|
|
219
|
+
*
|
|
220
|
+
* Security Rule: Incoming messages must exactly match the active host origin.
|
|
221
|
+
* This prevents malicious actors from spoofing origins via query parameters.
|
|
222
|
+
*
|
|
223
|
+
* @param eventOrigin - The origin from the MessageEvent
|
|
224
|
+
* @returns true if the origin is valid
|
|
225
|
+
*/
|
|
226
|
+
private isValidOrigin;
|
|
227
|
+
/**
|
|
228
|
+
* Set up the central message listener to handle responses from the LearnCard host
|
|
229
|
+
*/
|
|
230
|
+
private setupMessageListener;
|
|
231
|
+
/**
|
|
232
|
+
* Generate a unique request ID
|
|
233
|
+
*/
|
|
234
|
+
private generateRequestId;
|
|
235
|
+
/**
|
|
236
|
+
* Send a message to the parent window and return a Promise
|
|
237
|
+
*/
|
|
238
|
+
private sendMessage;
|
|
239
|
+
/**
|
|
240
|
+
* Request user identity (Single Sign-On)
|
|
241
|
+
*
|
|
242
|
+
* @returns Promise resolving to user identity including DID and JWT token
|
|
243
|
+
* @throws {LearnCardError} When user is not authenticated or request fails
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* const identity = await learnCard.requestIdentity();
|
|
248
|
+
* console.log('User DID:', identity.user.did);
|
|
249
|
+
* console.log('JWT Token:', identity.token);
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
requestIdentity(): Promise<IdentityResponse>;
|
|
253
|
+
/**
|
|
254
|
+
* Send a credential to the user's LearnCard wallet
|
|
255
|
+
*
|
|
256
|
+
* @param credential - The verifiable credential to send
|
|
257
|
+
* @returns Promise resolving to credential ID
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const response = await learnCard.sendCredential({
|
|
262
|
+
* '@context': ['https://www.w3.org/2018/credentials/v1'],
|
|
263
|
+
* type: ['VerifiableCredential'],
|
|
264
|
+
* credentialSubject: { id: 'did:example:123' }
|
|
265
|
+
* });
|
|
266
|
+
* console.log('Credential ID:', response.credentialId);
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
sendCredential(credential: unknown): Promise<SendCredentialResponse>;
|
|
270
|
+
/**
|
|
271
|
+
* Launch a feature in the LearnCard host application
|
|
272
|
+
*
|
|
273
|
+
* @param featurePath - Path to the feature (e.g., '/ai/topics')
|
|
274
|
+
* @param initialPrompt - Optional initial prompt or data
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* await learnCard.launchFeature(
|
|
279
|
+
* '/ai/topics?shortCircuitStep=newTopic',
|
|
280
|
+
* 'Help me understand cryptography'
|
|
281
|
+
* );
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
launchFeature(featurePath: string, initialPrompt?: string): Promise<void>;
|
|
285
|
+
/**
|
|
286
|
+
* Request credentials from the user's wallet using a query
|
|
287
|
+
*
|
|
288
|
+
* @param verifiablePresentationRequest - VPR with query criteria
|
|
289
|
+
* @returns Promise resolving to verifiable presentation
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```typescript
|
|
293
|
+
* const response = await learnCard.askCredentialSearch({
|
|
294
|
+
* query: [{
|
|
295
|
+
* type: 'QueryByTitle',
|
|
296
|
+
* credentialQuery: {
|
|
297
|
+
* reason: 'We need to verify your skills',
|
|
298
|
+
* title: 'JavaScript Expert'
|
|
299
|
+
* }
|
|
300
|
+
* }],
|
|
301
|
+
* challenge: 'challenge-123',
|
|
302
|
+
* domain: window.location.hostname
|
|
303
|
+
* });
|
|
304
|
+
*
|
|
305
|
+
* if (response.verifiablePresentation) {
|
|
306
|
+
* console.log('Received credentials:', response.verifiablePresentation.verifiableCredential);
|
|
307
|
+
* }
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
askCredentialSearch(verifiablePresentationRequest: VerifiablePresentationRequest): Promise<CredentialSearchResponse>;
|
|
311
|
+
/**
|
|
312
|
+
* Request a specific credential by ID
|
|
313
|
+
*
|
|
314
|
+
* @param credentialId - The ID of the credential to request
|
|
315
|
+
* @returns Promise resolving to the credential
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* const response = await learnCard.askCredentialSpecific('credential-id-123');
|
|
320
|
+
* if (response.credential) {
|
|
321
|
+
* console.log('Received credential:', response.credential);
|
|
322
|
+
* }
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
askCredentialSpecific(credentialId: string): Promise<CredentialSpecificResponse>;
|
|
326
|
+
/**
|
|
327
|
+
* Request user consent for permissions
|
|
328
|
+
*
|
|
329
|
+
* @param contractUri - URI of the consent contract
|
|
330
|
+
* @returns Promise resolving to consent response
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const response = await learnCard.requestConsent('lc:network:network.learncard.com/trpc:contract:abc123');
|
|
335
|
+
* if (response.granted) {
|
|
336
|
+
* console.log('User granted consent');
|
|
337
|
+
* }
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
requestConsent(contractUri: string): Promise<ConsentResponse>;
|
|
341
|
+
/**
|
|
342
|
+
* Initiate a template-based credential issuance flow
|
|
343
|
+
*
|
|
344
|
+
* @param templateId - ID of the template/boost to issue
|
|
345
|
+
* @param draftRecipients - Optional array of recipient DIDs
|
|
346
|
+
* @returns Promise resolving to template issue response
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* const response = await learnCard.initiateTemplateIssue(
|
|
351
|
+
* 'lc:network:network.learncard.com/trpc:boost:xyz789',
|
|
352
|
+
* ['did:key:abc', 'did:key:def']
|
|
353
|
+
* );
|
|
354
|
+
*
|
|
355
|
+
* if (response.issued) {
|
|
356
|
+
* console.log('Template issued successfully');
|
|
357
|
+
* }
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
initiateTemplateIssue(templateId: string, draftRecipients?: string[]): Promise<TemplateIssueResponse>;
|
|
361
|
+
/**
|
|
362
|
+
* Clean up the SDK and remove event listeners
|
|
363
|
+
*/
|
|
364
|
+
destroy(): void;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Factory function to create a PartnerConnect instance
|
|
368
|
+
*
|
|
369
|
+
* @param options - Configuration options
|
|
370
|
+
* @returns PartnerConnect instance
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* const learnCard = createPartnerConnect({
|
|
375
|
+
* hostOrigin: 'https://learncard.app',
|
|
376
|
+
* protocol: 'LEARNCARD_V1',
|
|
377
|
+
* requestTimeout: 30000
|
|
378
|
+
* });
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
declare function createPartnerConnect(options: PartnerConnectOptions): PartnerConnect;
|
|
382
|
+
|
|
383
|
+
export { ConsentResponse, CredentialSearchResponse, CredentialSpecificResponse, ErrorCode, IdentityResponse, LearnCardError, PartnerConnect, PartnerConnectOptions, PendingRequest, PostMessageRequest, PostMessageResponse, SendCredentialResponse, TemplateIssueResponse, VPRQuery, VerifiablePresentationRequest, createPartnerConnect, createPartnerConnect as default };
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
const _PartnerConnect = class _PartnerConnect {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
__publicField(this, "hostOrigins", ["https://learncard.app"]);
|
|
7
|
+
__publicField(this, "activeHostOrigin", "https://learncard.app");
|
|
8
|
+
__publicField(this, "allowNativeAppOrigins", true);
|
|
9
|
+
__publicField(this, "protocol", "LEARNCARD_V1");
|
|
10
|
+
__publicField(this, "requestTimeout", 3e4);
|
|
11
|
+
__publicField(this, "pendingRequests");
|
|
12
|
+
__publicField(this, "messageListener", null);
|
|
13
|
+
__publicField(this, "isInitialized", false);
|
|
14
|
+
var _a;
|
|
15
|
+
const hostOrigin = (options == null ? void 0 : options.hostOrigin) || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
16
|
+
this.hostOrigins = Array.isArray(hostOrigin) ? hostOrigin : [hostOrigin];
|
|
17
|
+
this.protocol = (options == null ? void 0 : options.protocol) || "LEARNCARD_V1";
|
|
18
|
+
this.requestTimeout = (options == null ? void 0 : options.requestTimeout) || 3e4;
|
|
19
|
+
this.allowNativeAppOrigins = (_a = options == null ? void 0 : options.allowNativeAppOrigins) != null ? _a : true;
|
|
20
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
21
|
+
this.configureActiveOrigin();
|
|
22
|
+
this.setupMessageListener();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Configure the active host origin using the following hierarchy:
|
|
26
|
+
* 1. Check for `lc_host_override` query parameter (for staging/testing)
|
|
27
|
+
* 2. Fall back to first configured origin
|
|
28
|
+
* 3. Fall back to DEFAULT_HOST_ORIGIN
|
|
29
|
+
*
|
|
30
|
+
* This origin will be used for all outgoing messages and incoming message validation.
|
|
31
|
+
*/
|
|
32
|
+
configureActiveOrigin() {
|
|
33
|
+
if (typeof window === "undefined") {
|
|
34
|
+
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
39
|
+
const hostOverride = urlParams.get("lc_host_override");
|
|
40
|
+
if (hostOverride) {
|
|
41
|
+
if (this.hostOrigins.length > 0 && !this.isOriginInWhitelist(hostOverride)) {
|
|
42
|
+
console.warn(
|
|
43
|
+
"[LearnCard SDK] lc_host_override value is not in the configured whitelist:",
|
|
44
|
+
hostOverride,
|
|
45
|
+
"Allowed:",
|
|
46
|
+
this.hostOrigins
|
|
47
|
+
);
|
|
48
|
+
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
49
|
+
} else {
|
|
50
|
+
this.activeHostOrigin = hostOverride;
|
|
51
|
+
console.log("[LearnCard SDK] Using lc_host_override:", hostOverride);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
55
|
+
console.log("[LearnCard SDK] Using configured origin:", this.activeHostOrigin);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("[LearnCard SDK] Error configuring active origin:", error);
|
|
59
|
+
this.activeHostOrigin = this.hostOrigins[0] || _PartnerConnect.DEFAULT_HOST_ORIGIN;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
isOriginNativeApp(origin) {
|
|
63
|
+
return origin.startsWith("capacitor://") || origin.startsWith("ionic://") || origin.startsWith("https://localhost") || origin.startsWith("http://localhost") || origin.startsWith("http://127.0.0.1") || origin.startsWith("http://192.168.1.164");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if an origin is in the configured whitelist
|
|
67
|
+
*/
|
|
68
|
+
isOriginInWhitelist(origin) {
|
|
69
|
+
return this.hostOrigins.includes(origin) || this.allowNativeAppOrigins && this.isOriginNativeApp(origin);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if an event origin is valid against the active host origin
|
|
73
|
+
*
|
|
74
|
+
* Security Rule: Incoming messages must exactly match the active host origin.
|
|
75
|
+
* This prevents malicious actors from spoofing origins via query parameters.
|
|
76
|
+
*
|
|
77
|
+
* @param eventOrigin - The origin from the MessageEvent
|
|
78
|
+
* @returns true if the origin is valid
|
|
79
|
+
*/
|
|
80
|
+
isValidOrigin(eventOrigin) {
|
|
81
|
+
return eventOrigin === this.activeHostOrigin;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Set up the central message listener to handle responses from the LearnCard host
|
|
85
|
+
*/
|
|
86
|
+
setupMessageListener() {
|
|
87
|
+
if (typeof window === "undefined") {
|
|
88
|
+
throw new Error("PartnerConnect SDK can only be used in a browser environment");
|
|
89
|
+
}
|
|
90
|
+
this.messageListener = (event) => {
|
|
91
|
+
if (!this.isValidOrigin(event.origin)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const data = event.data;
|
|
95
|
+
if (data.protocol !== this.protocol || !data.requestId) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const pending = this.pendingRequests.get(data.requestId);
|
|
99
|
+
if (!pending) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
clearTimeout(pending.timeoutId);
|
|
103
|
+
this.pendingRequests.delete(data.requestId);
|
|
104
|
+
if (data.type === "SUCCESS") {
|
|
105
|
+
pending.resolve(data.data);
|
|
106
|
+
} else if (data.type === "ERROR") {
|
|
107
|
+
pending.reject(data.error || { code: "UNKNOWN_ERROR", message: "An unknown error occurred" });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
window.addEventListener("message", this.messageListener);
|
|
111
|
+
this.isInitialized = true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generate a unique request ID
|
|
115
|
+
*/
|
|
116
|
+
generateRequestId(action) {
|
|
117
|
+
return `${action}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Send a message to the parent window and return a Promise
|
|
121
|
+
*/
|
|
122
|
+
sendMessage(action, payload) {
|
|
123
|
+
if (!this.isInitialized) {
|
|
124
|
+
return Promise.reject({
|
|
125
|
+
code: "SDK_NOT_INITIALIZED",
|
|
126
|
+
message: "SDK is not initialized"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const requestId = this.generateRequestId(action);
|
|
131
|
+
const timeoutId = setTimeout(() => {
|
|
132
|
+
if (this.pendingRequests.has(requestId)) {
|
|
133
|
+
this.pendingRequests.delete(requestId);
|
|
134
|
+
reject({
|
|
135
|
+
code: "LC_TIMEOUT",
|
|
136
|
+
message: `Request ${action} timed out after ${this.requestTimeout}ms`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}, this.requestTimeout);
|
|
140
|
+
this.pendingRequests.set(requestId, {
|
|
141
|
+
resolve,
|
|
142
|
+
reject,
|
|
143
|
+
timeoutId
|
|
144
|
+
});
|
|
145
|
+
const message = {
|
|
146
|
+
protocol: this.protocol,
|
|
147
|
+
action,
|
|
148
|
+
requestId,
|
|
149
|
+
payload
|
|
150
|
+
};
|
|
151
|
+
window.parent.postMessage(message, this.activeHostOrigin);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Request user identity (Single Sign-On)
|
|
156
|
+
*
|
|
157
|
+
* @returns Promise resolving to user identity including DID and JWT token
|
|
158
|
+
* @throws {LearnCardError} When user is not authenticated or request fails
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const identity = await learnCard.requestIdentity();
|
|
163
|
+
* console.log('User DID:', identity.user.did);
|
|
164
|
+
* console.log('JWT Token:', identity.token);
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
requestIdentity() {
|
|
168
|
+
return this.sendMessage("REQUEST_IDENTITY", { challenge: `${Date.now()}-${Math.random().toString(36).substring(2, 9)}` });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Send a credential to the user's LearnCard wallet
|
|
172
|
+
*
|
|
173
|
+
* @param credential - The verifiable credential to send
|
|
174
|
+
* @returns Promise resolving to credential ID
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* const response = await learnCard.sendCredential({
|
|
179
|
+
* '@context': ['https://www.w3.org/2018/credentials/v1'],
|
|
180
|
+
* type: ['VerifiableCredential'],
|
|
181
|
+
* credentialSubject: { id: 'did:example:123' }
|
|
182
|
+
* });
|
|
183
|
+
* console.log('Credential ID:', response.credentialId);
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
sendCredential(credential) {
|
|
187
|
+
return this.sendMessage("SEND_CREDENTIAL", { credential });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Launch a feature in the LearnCard host application
|
|
191
|
+
*
|
|
192
|
+
* @param featurePath - Path to the feature (e.g., '/ai/topics')
|
|
193
|
+
* @param initialPrompt - Optional initial prompt or data
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* await learnCard.launchFeature(
|
|
198
|
+
* '/ai/topics?shortCircuitStep=newTopic',
|
|
199
|
+
* 'Help me understand cryptography'
|
|
200
|
+
* );
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
launchFeature(featurePath, initialPrompt) {
|
|
204
|
+
return this.sendMessage("LAUNCH_FEATURE", { featurePath, initialPrompt });
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Request credentials from the user's wallet using a query
|
|
208
|
+
*
|
|
209
|
+
* @param verifiablePresentationRequest - VPR with query criteria
|
|
210
|
+
* @returns Promise resolving to verifiable presentation
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const response = await learnCard.askCredentialSearch({
|
|
215
|
+
* query: [{
|
|
216
|
+
* type: 'QueryByTitle',
|
|
217
|
+
* credentialQuery: {
|
|
218
|
+
* reason: 'We need to verify your skills',
|
|
219
|
+
* title: 'JavaScript Expert'
|
|
220
|
+
* }
|
|
221
|
+
* }],
|
|
222
|
+
* challenge: 'challenge-123',
|
|
223
|
+
* domain: window.location.hostname
|
|
224
|
+
* });
|
|
225
|
+
*
|
|
226
|
+
* if (response.verifiablePresentation) {
|
|
227
|
+
* console.log('Received credentials:', response.verifiablePresentation.verifiableCredential);
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
askCredentialSearch(verifiablePresentationRequest) {
|
|
232
|
+
return this.sendMessage("ASK_CREDENTIAL_SEARCH", {
|
|
233
|
+
verifiablePresentationRequest
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Request a specific credential by ID
|
|
238
|
+
*
|
|
239
|
+
* @param credentialId - The ID of the credential to request
|
|
240
|
+
* @returns Promise resolving to the credential
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* const response = await learnCard.askCredentialSpecific('credential-id-123');
|
|
245
|
+
* if (response.credential) {
|
|
246
|
+
* console.log('Received credential:', response.credential);
|
|
247
|
+
* }
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
askCredentialSpecific(credentialId) {
|
|
251
|
+
return this.sendMessage("ASK_CREDENTIAL_SPECIFIC", {
|
|
252
|
+
credentialId
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Request user consent for permissions
|
|
257
|
+
*
|
|
258
|
+
* @param contractUri - URI of the consent contract
|
|
259
|
+
* @returns Promise resolving to consent response
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* const response = await learnCard.requestConsent('lc:network:network.learncard.com/trpc:contract:abc123');
|
|
264
|
+
* if (response.granted) {
|
|
265
|
+
* console.log('User granted consent');
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
requestConsent(contractUri) {
|
|
270
|
+
return this.sendMessage("REQUEST_CONSENT", { contractUri });
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Initiate a template-based credential issuance flow
|
|
274
|
+
*
|
|
275
|
+
* @param templateId - ID of the template/boost to issue
|
|
276
|
+
* @param draftRecipients - Optional array of recipient DIDs
|
|
277
|
+
* @returns Promise resolving to template issue response
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* const response = await learnCard.initiateTemplateIssue(
|
|
282
|
+
* 'lc:network:network.learncard.com/trpc:boost:xyz789',
|
|
283
|
+
* ['did:key:abc', 'did:key:def']
|
|
284
|
+
* );
|
|
285
|
+
*
|
|
286
|
+
* if (response.issued) {
|
|
287
|
+
* console.log('Template issued successfully');
|
|
288
|
+
* }
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
initiateTemplateIssue(templateId, draftRecipients) {
|
|
292
|
+
return this.sendMessage("INITIATE_TEMPLATE_ISSUE", {
|
|
293
|
+
templateId,
|
|
294
|
+
draftRecipients: draftRecipients || []
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Clean up the SDK and remove event listeners
|
|
299
|
+
*/
|
|
300
|
+
destroy() {
|
|
301
|
+
if (this.messageListener) {
|
|
302
|
+
window.removeEventListener("message", this.messageListener);
|
|
303
|
+
this.messageListener = null;
|
|
304
|
+
}
|
|
305
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
306
|
+
clearTimeout(pending.timeoutId);
|
|
307
|
+
pending.reject({
|
|
308
|
+
code: "SDK_DESTROYED",
|
|
309
|
+
message: "SDK was destroyed before request completed"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
this.pendingRequests.clear();
|
|
313
|
+
this.isInitialized = false;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
/** Default host origin (security anchor) */
|
|
317
|
+
__publicField(_PartnerConnect, "DEFAULT_HOST_ORIGIN", "https://learncard.app");
|
|
318
|
+
let PartnerConnect = _PartnerConnect;
|
|
319
|
+
function createPartnerConnect(options) {
|
|
320
|
+
return new PartnerConnect(options);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export { PartnerConnect, createPartnerConnect, createPartnerConnect as default };
|
|
324
|
+
//# sourceMappingURL=partner-connect.esm.js.map
|