@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 CHANGED
@@ -635,28 +635,28 @@ try {
635
635
  ---
636
636
  // src/pages/index.astro
637
637
  const config = {
638
- learnCardHostOrigin: import.meta.env.PUBLIC_LEARNCARD_HOST || 'https://learncard.app'
638
+ learnCardHostOrigin: import.meta.env.PUBLIC_LEARNCARD_HOST || 'https://learncard.app',
639
639
  };
640
640
  ---
641
641
 
642
642
  <script>
643
- import { createPartnerConnect } from '@learncard/partner-connect';
644
-
645
- const config = window.__LC_CONFIG;
646
- const learnCard = createPartnerConnect({
647
- hostOrigin: config.learnCardHostOrigin
648
- });
649
-
650
- async function init() {
651
- try {
652
- const identity = await learnCard.requestIdentity();
653
- console.log('Logged in as:', identity.user.did);
654
- } catch (error) {
655
- console.error('Not authenticated:', error);
643
+ import { createPartnerConnect } from '@learncard/partner-connect';
644
+
645
+ const config = window.__LC_CONFIG;
646
+ const learnCard = createPartnerConnect({
647
+ hostOrigin: config.learnCardHostOrigin,
648
+ });
649
+
650
+ async function init() {
651
+ try {
652
+ const identity = await learnCard.requestIdentity();
653
+ console.log('Logged in as:', identity.user.did);
654
+ } catch (error) {
655
+ console.error('Not authenticated:', error);
656
+ }
656
657
  }
657
- }
658
658
 
659
- init();
659
+ init();
660
660
  </script>
661
661
  ```
662
662
 
@@ -736,6 +736,38 @@ import type {
736
736
  } from '@learncard/partner-connect';
737
737
  ```
738
738
 
739
+ ## Learner Context Sync Readiness
740
+
741
+ Apps that need a complete learner snapshot can ask LearnCard to wait for background data sync:
742
+
743
+ ```typescript
744
+ const context = await learnCard.requestLearnerContext({
745
+ includeCredentials: true,
746
+ waitForSync: true,
747
+ format: 'structured',
748
+ });
749
+
750
+ if (context.status === 'syncing') {
751
+ const unsubscribe = learnCard.onSyncComplete(async () => {
752
+ const readyContext = await learnCard.requestLearnerContext({
753
+ includeCredentials: true,
754
+ waitForSync: true,
755
+ format: 'structured',
756
+ });
757
+
758
+ unsubscribe();
759
+ console.log(readyContext.raw?.credentials);
760
+ });
761
+ }
762
+ ```
763
+
764
+ Use `learnCard.getSyncStatus()` to render your own progress UI:
765
+
766
+ ```typescript
767
+ const syncStatus = await learnCard.getSyncStatus();
768
+ console.log(syncStatus.status, syncStatus.progress);
769
+ ```
770
+
739
771
  ## License
740
772
 
741
773
  MIT
package/dist/index.d.ts CHANGED
@@ -312,6 +312,12 @@ interface RequestLearnerContextOptions {
312
312
  * @default 'compact'
313
313
  */
314
314
  detailLevel?: 'compact' | 'expanded';
315
+ /**
316
+ * Wait for LearnCard to finish background ConsentFlow data sync before returning context.
317
+ * Apps that need a complete learner snapshot should set this to true.
318
+ * @default false
319
+ */
320
+ waitForSync?: boolean;
315
321
  }
316
322
  /**
317
323
  * Raw data included in learner context response (when format is 'structured')
@@ -326,6 +332,10 @@ interface LearnerContextRawData {
326
332
  * Response from REQUEST_LEARNER_CONTEXT action
327
333
  */
328
334
  interface LearnerContextResponse {
335
+ /** Whether the response used immediately available data or waited for a complete sync */
336
+ status?: 'ready' | 'syncing';
337
+ /** Current sync progress when available */
338
+ progress?: SyncProgress;
329
339
  /** LLM-ready formatted prompt text */
330
340
  prompt: string;
331
341
  /** Raw structured data (only included when format is 'structured') */
@@ -335,6 +345,18 @@ interface LearnerContextResponse {
335
345
  /** User's display name if available */
336
346
  displayName?: string;
337
347
  }
348
+ interface SyncProgress {
349
+ totalCredentials: number;
350
+ completedCredentials: number;
351
+ failedCredentials: number;
352
+ retryCount: number;
353
+ }
354
+ interface SyncStatus {
355
+ status: 'ready' | 'syncing' | 'error';
356
+ progress: SyncProgress;
357
+ eta?: number;
358
+ lastError?: string;
359
+ }
338
360
  /**
339
361
  * Keywords for next steps in summary credential data
340
362
  */
@@ -587,6 +609,8 @@ declare class PartnerConnect {
587
609
  private pendingRequests;
588
610
  private messageListener;
589
611
  private isInitialized;
612
+ private syncCompleteCallbacks;
613
+ private syncStatusPollId;
590
614
  constructor(options?: PartnerConnectOptions);
591
615
  /**
592
616
  * Configure the active host origin using the following hierarchy:
@@ -889,11 +913,19 @@ declare class PartnerConnect {
889
913
  * const context = await learnCard.requestLearnerContext({
890
914
  * includeCredentials: true,
891
915
  * includePersonalData: true,
916
+ * waitForSync: true,
892
917
  * format: 'prompt',
893
918
  * instructions: 'Focus on technical skills and certifications',
894
919
  * detailLevel: 'expanded'
895
920
  * });
896
921
  *
922
+ * if (context.status === 'syncing') {
923
+ * const unsubscribe = learnCard.onSyncComplete(async () => {
924
+ * const readyContext = await learnCard.requestLearnerContext({ waitForSync: true });
925
+ * unsubscribe();
926
+ * });
927
+ * }
928
+ *
897
929
  * // Use in AI system prompt
898
930
  * const systemPrompt = `You are a helpful tutor. ${context.prompt}`;
899
931
  *
@@ -903,6 +935,18 @@ declare class PartnerConnect {
903
935
  * ```
904
936
  */
905
937
  requestLearnerContext(options?: RequestLearnerContextOptions): Promise<LearnerContextResponse>;
938
+ /**
939
+ * Get the current LearnCard background data sync status.
940
+ */
941
+ getSyncStatus(): Promise<SyncStatus>;
942
+ /**
943
+ * Register a callback that fires when LearnCard reports background sync has reached a
944
+ * terminal state ('ready' or 'error'). Check `status.status` to distinguish the two.
945
+ * Polling stops once a terminal state is reached, all callbacks unsubscribe, or the
946
+ * poll exceeds its maximum duration (reported to callbacks as an 'error' status).
947
+ * Returns an unsubscribe function.
948
+ */
949
+ onSyncComplete(callback: (status: SyncStatus) => void): () => void;
906
950
  /**
907
951
  * Send a generic event to be processed by the brain service on behalf of this app.
908
952
  * This is used for backend-like operations such as issuing credentials.
@@ -977,4 +1021,4 @@ declare class PartnerConnect {
977
1021
  */
978
1022
  declare function createPartnerConnect(options?: PartnerConnectOptions): PartnerConnect;
979
1023
 
980
- export { AppNotificationInput, AppNotificationResponse, CheckCredentialInput, CheckCredentialResponse, CheckIssuanceStatusInput, ConsentResponse, CredentialSearchResponse, CredentialSpecificResponse, ErrorCode, GetCounterResponse, GetCountersResponse, GetTemplateRecipientsInput, IdentityResponse, IncrementCounterResponse, LearnCardError, LearnerContextRawData, LearnerContextResponse, PartnerConnect, PartnerConnectError, PartnerConnectOptions, PendingRequest, PostMessageRequest, PostMessageResponse, RequestConsentOptions, RequestConsentPayload, RequestLearnerContextOptions, SendAiSessionCredentialInput, SendAiSessionCredentialResponse, SendCredentialResponse, SummaryCredentialData, SummaryCredentialKeyword, SummaryCredentialNextStep, SummaryCredentialReflection, SummaryCredentialSkill, TemplateCredentialInput, TemplateCredentialResponse, TemplateIssuanceStatusResponse, TemplateIssueResponse, TemplateRecipientRecord, TemplateRecipientsResponse, VPRQuery, VerifiablePresentationRequest, createPartnerConnect, createPartnerConnect as default };
1024
+ export { AppNotificationInput, AppNotificationResponse, CheckCredentialInput, CheckCredentialResponse, CheckIssuanceStatusInput, ConsentResponse, CredentialSearchResponse, CredentialSpecificResponse, ErrorCode, GetCounterResponse, GetCountersResponse, GetTemplateRecipientsInput, IdentityResponse, IncrementCounterResponse, LearnCardError, LearnerContextRawData, LearnerContextResponse, PartnerConnect, PartnerConnectError, PartnerConnectOptions, PendingRequest, PostMessageRequest, PostMessageResponse, RequestConsentOptions, RequestConsentPayload, RequestLearnerContextOptions, SendAiSessionCredentialInput, SendAiSessionCredentialResponse, SendCredentialResponse, SummaryCredentialData, SummaryCredentialKeyword, SummaryCredentialNextStep, SummaryCredentialReflection, SummaryCredentialSkill, SyncProgress, SyncStatus, TemplateCredentialInput, TemplateCredentialResponse, TemplateIssuanceStatusResponse, TemplateIssueResponse, TemplateRecipientRecord, TemplateRecipientsResponse, VPRQuery, VerifiablePresentationRequest, createPartnerConnect, createPartnerConnect as default };
@@ -34,6 +34,7 @@ class PartnerConnectError extends Error {
34
34
  var __defProp = Object.defineProperty;
35
35
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
36
36
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
37
+ const SYNC_STATUS_POLL_MAX_DURATION_MS = 10 * 60 * 1e3;
37
38
  const _PartnerConnect = class _PartnerConnect {
38
39
  constructor(options) {
39
40
  __publicField(this, "hostOrigins", ["https://learncard.app"]);
@@ -44,6 +45,8 @@ const _PartnerConnect = class _PartnerConnect {
44
45
  __publicField(this, "pendingRequests");
45
46
  __publicField(this, "messageListener", null);
46
47
  __publicField(this, "isInitialized", false);
48
+ __publicField(this, "syncCompleteCallbacks", /* @__PURE__ */ new Set());
49
+ __publicField(this, "syncStatusPollId", null);
47
50
  var _a, _b;
48
51
  const hostOrigin = (_a = options == null ? void 0 : options.hostOrigin) != null ? _a : _PartnerConnect.DEFAULT_HOST_ORIGIN;
49
52
  const configured = Array.isArray(hostOrigin) ? hostOrigin : [hostOrigin];
@@ -162,10 +165,7 @@ const _PartnerConnect = class _PartnerConnect {
162
165
  let candidateUrl;
163
166
  try {
164
167
  patternUrl = new URL(
165
- pattern.replace(
166
- _PartnerConnect.WILDCARD_REGEX,
167
- _PartnerConnect.WILDCARD_PLACEHOLDER
168
- )
168
+ pattern.replace(_PartnerConnect.WILDCARD_REGEX, _PartnerConnect.WILDCARD_PLACEHOLDER)
169
169
  );
170
170
  candidateUrl = new URL(candidate);
171
171
  } catch {
@@ -562,11 +562,19 @@ const _PartnerConnect = class _PartnerConnect {
562
562
  * const context = await learnCard.requestLearnerContext({
563
563
  * includeCredentials: true,
564
564
  * includePersonalData: true,
565
+ * waitForSync: true,
565
566
  * format: 'prompt',
566
567
  * instructions: 'Focus on technical skills and certifications',
567
568
  * detailLevel: 'expanded'
568
569
  * });
569
570
  *
571
+ * if (context.status === 'syncing') {
572
+ * const unsubscribe = learnCard.onSyncComplete(async () => {
573
+ * const readyContext = await learnCard.requestLearnerContext({ waitForSync: true });
574
+ * unsubscribe();
575
+ * });
576
+ * }
577
+ *
570
578
  * // Use in AI system prompt
571
579
  * const systemPrompt = `You are a helpful tutor. ${context.prompt}`;
572
580
  *
@@ -576,15 +584,70 @@ const _PartnerConnect = class _PartnerConnect {
576
584
  * ```
577
585
  */
578
586
  requestLearnerContext(options) {
579
- var _a, _b, _c, _d;
587
+ var _a, _b, _c, _d, _e;
580
588
  return this.sendMessage("REQUEST_LEARNER_CONTEXT", {
581
589
  includeCredentials: (_a = options == null ? void 0 : options.includeCredentials) != null ? _a : true,
582
590
  includePersonalData: (_b = options == null ? void 0 : options.includePersonalData) != null ? _b : false,
583
591
  format: (_c = options == null ? void 0 : options.format) != null ? _c : "prompt",
584
592
  instructions: options == null ? void 0 : options.instructions,
585
- detailLevel: (_d = options == null ? void 0 : options.detailLevel) != null ? _d : "compact"
593
+ detailLevel: (_d = options == null ? void 0 : options.detailLevel) != null ? _d : "compact",
594
+ waitForSync: (_e = options == null ? void 0 : options.waitForSync) != null ? _e : false
586
595
  });
587
596
  }
597
+ /**
598
+ * Get the current LearnCard background data sync status.
599
+ */
600
+ getSyncStatus() {
601
+ return this.sendMessage("GET_SYNC_STATUS");
602
+ }
603
+ /**
604
+ * Register a callback that fires when LearnCard reports background sync has reached a
605
+ * terminal state ('ready' or 'error'). Check `status.status` to distinguish the two.
606
+ * Polling stops once a terminal state is reached, all callbacks unsubscribe, or the
607
+ * poll exceeds its maximum duration (reported to callbacks as an 'error' status).
608
+ * Returns an unsubscribe function.
609
+ */
610
+ onSyncComplete(callback) {
611
+ this.syncCompleteCallbacks.add(callback);
612
+ if (!this.syncStatusPollId) {
613
+ const pollStartedAt = Date.now();
614
+ const stopPolling = () => {
615
+ if (this.syncStatusPollId) {
616
+ clearInterval(this.syncStatusPollId);
617
+ this.syncStatusPollId = null;
618
+ }
619
+ };
620
+ this.syncStatusPollId = setInterval(() => {
621
+ if (Date.now() - pollStartedAt > SYNC_STATUS_POLL_MAX_DURATION_MS) {
622
+ stopPolling();
623
+ const timeoutStatus = {
624
+ status: "error",
625
+ progress: {
626
+ totalCredentials: 0,
627
+ completedCredentials: 0,
628
+ failedCredentials: 0,
629
+ retryCount: 0
630
+ },
631
+ lastError: "Timed out waiting for sync to complete"
632
+ };
633
+ this.syncCompleteCallbacks.forEach((cb) => cb(timeoutStatus));
634
+ return;
635
+ }
636
+ this.getSyncStatus().then((status) => {
637
+ if (status.status !== "ready" && status.status !== "error") return;
638
+ stopPolling();
639
+ this.syncCompleteCallbacks.forEach((cb) => cb(status));
640
+ }).catch(() => void 0);
641
+ }, 1e3);
642
+ }
643
+ return () => {
644
+ this.syncCompleteCallbacks.delete(callback);
645
+ if (this.syncCompleteCallbacks.size === 0 && this.syncStatusPollId) {
646
+ clearInterval(this.syncStatusPollId);
647
+ this.syncStatusPollId = null;
648
+ }
649
+ };
650
+ }
588
651
  /**
589
652
  * Send a generic event to be processed by the brain service on behalf of this app.
590
653
  * This is used for backend-like operations such as issuing credentials.
@@ -683,6 +746,11 @@ const _PartnerConnect = class _PartnerConnect {
683
746
  );
684
747
  }
685
748
  this.pendingRequests.clear();
749
+ if (this.syncStatusPollId) {
750
+ clearInterval(this.syncStatusPollId);
751
+ this.syncStatusPollId = null;
752
+ }
753
+ this.syncCompleteCallbacks.clear();
686
754
  this.isInitialized = false;
687
755
  }
688
756
  };