@mindline/sync 1.0.40 → 1.0.42

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.
@@ -2,5 +2,6 @@
2
2
  "ExpandedNodes": [
3
3
  ""
4
4
  ],
5
+ "SelectedNode": "\\index.ts",
5
6
  "PreviewInSolutionExplorer": false
6
7
  }
package/.vs/slnx.sqlite CHANGED
Binary file
Binary file
package/index.d.ts CHANGED
@@ -26,6 +26,7 @@ declare module "@mindline/sync" {
26
26
  loginHint: string;
27
27
  scopes: string[];
28
28
  authTS: Date;
29
+ claimsprincipal: string; // claims principal cached at login to allow clearing cache at logout
29
30
  constructor();
30
31
  }
31
32
  // tenant (Azure AD tenant, AD domain, Google workspace)
@@ -104,7 +105,7 @@ declare module "@mindline/sync" {
104
105
  export type TaskType = "initialization" |
105
106
  "authenticate user" |
106
107
  "reload React" |
107
- "PUT access token" |
108
+ "PUT tenant" |
108
109
  "GET tenant details" |
109
110
  "POST config init" |
110
111
  "GET workspaces" |
@@ -203,6 +204,7 @@ declare module "@mindline/sync" {
203
204
  read: number;
204
205
  written: number;
205
206
  deferred: number;
207
+ nothingtosync: boolean;
206
208
  targets: TenantNode[];
207
209
  constructor(tid: string, name: string);
208
210
  update(total: number, read: number, written: number, deferred: number): void;
@@ -220,7 +222,7 @@ declare module "@mindline/sync" {
220
222
  export function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{groups: Group[], error: string}>;
221
223
  export function signIn(user: User, tasks: TaskArray): void;
222
224
  export function signInIncrementally(user: User, scope: string): void;
223
- export function signOut(user: User): void;
225
+ export function signOut(user: User): boolean;
224
226
  export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
225
227
  export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
226
228
  export function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): Promise<boolean>;
package/index.ts CHANGED
@@ -732,22 +732,32 @@ export class BatchArray {
732
732
  this.pb_idleMax = 0;
733
733
  setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
734
734
  this.pb_timer = setInterval(() => {
735
- // if we go 20 seconds without a signalR message, finish the sync
736
- this.pb_idle = this.pb_idle + 1;
737
- this.pb_idleMax = Math.max(this.pb_idle, this.pb_idleMax);
738
- setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
739
- if (this.pb_idle >= 20) {
735
+ // if signalR has finished the sync, stop the timer
736
+ if (this.milestoneArray.milestones[0].Write != null) {
740
737
  clearInterval(this.pb_timer);
741
738
  this.pb_timer = null;
742
- if (this.milestoneArray.milestones[0].Write == null) {
743
- this.milestoneArray.write(setMilestones);
744
- }
745
- setConfigSyncResult(`finished sync, no updates for ${this.pb_idle} seconds`);
746
- }
747
- // if we get to 100, stop the timer, let SignalR or countdown timer finish sync
748
- if (this.pb_progress < 100) {
749
- this.pb_progress = Math.min(100, this.pb_progress + this.pb_increment);
739
+ this.pb_progress = 100;
750
740
  setSyncProgress(this.pb_progress);
741
+ setIdleText(`Complete. [max idle: ${this.pb_idleMax}]`);
742
+ }
743
+ else {
744
+ // if we've gone 30 seconds without a signalR message, finish the sync
745
+ this.pb_idle = this.pb_idle + 1;
746
+ this.pb_idleMax = Math.max(this.pb_idle, this.pb_idleMax);
747
+ setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
748
+ if (this.pb_idle >= 60) {
749
+ clearInterval(this.pb_timer);
750
+ this.pb_timer = null;
751
+ if (this.milestoneArray.milestones[0].Write == null) {
752
+ this.milestoneArray.write(setMilestones);
753
+ setConfigSyncResult(`finished sync, no updates for ${this.pb_idle} seconds`);
754
+ }
755
+ }
756
+ // if we get to 100, the progress bar stops but SignalR or countdown timer completes the sync
757
+ if (this.pb_progress < 100) {
758
+ this.pb_progress = Math.min(100, this.pb_progress + this.pb_increment);
759
+ setSyncProgress(this.pb_progress);
760
+ }
751
761
  }
752
762
  }, 1000);
753
763
  this.milestoneArray.start(setMilestones);
@@ -803,10 +813,13 @@ export class BatchArray {
803
813
  return;
804
814
  }
805
815
  tenantNode.batchId = matchingPair.BatchId;
806
- // process stats for this SignalR message batch
816
+ // process stats for this SignalR message (one batch per tenant node)
807
817
  let statsarray = item.Stats; // get the array of statistics
808
818
  let statskeys = Object.keys(statsarray); // get the keys of the array
809
819
  let statsvalues = Object.values(statsarray); // get the values of the array
820
+ // does this tenantnode/batch have nothing to sync?
821
+ let bTotalCountZero: boolean = false;
822
+ let bCurrentCountZero: boolean = false;
810
823
  for (let j = 0; j < statskeys.length; j++) {
811
824
  let bTotalCount = statskeys[j].endsWith("TotalCount");
812
825
  let bCurrentCount = statskeys[j].endsWith("CurrentCount");
@@ -823,15 +836,18 @@ export class BatchArray {
823
836
  return;
824
837
  }
825
838
  if (bTotalCount) {
839
+ bTotalCountZero = Number(statsvalues[j]) == 0;
826
840
  tenantNode.total = Math.max(Number(statsvalues[j]), tenantNode.total);
827
841
  console.log(`----- ${tenantNode.name} TID: ${tenantNode.tid} batchId: ${tenantNode.batchId}`);
828
842
  console.log(`----- ${tenantNode.name} Total To Read: ${tenantNode.total}`);
829
843
  }
830
844
  else {
845
+ bCurrentCountZero = Number(statsvalues[j]) == 0;
831
846
  tenantNode.read = Math.max(Number(statsvalues[j]), tenantNode.read);
832
847
  console.log(`----- ${tenantNode.name} Currently Read: ${tenantNode.read}`);
833
848
  }
834
849
  }
850
+ tenantNode.nothingtosync = bTotalCountZero && bCurrentCountZero;
835
851
  if (statskeys[j].startsWith("Writer")) {
836
852
  // parse tid from Writer key
837
853
  let tidRegexp = /Writer\/TID:(.+)\/TotalCount/;
@@ -878,6 +894,7 @@ export class BatchArray {
878
894
  let bReadingComplete: boolean = true;
879
895
  let bWritingComplete: boolean = true;
880
896
  let bWritingStarted: boolean = false;
897
+ let bNothingToSync: boolean = true;
881
898
  let readerTotal: number = 0;
882
899
  let readerCurrent: number = 0;
883
900
  let writerTotal: number = 0;
@@ -889,6 +906,7 @@ export class BatchArray {
889
906
  writerTotal += Math.max(writerNode.total, sourceTenantNode.total);
890
907
  writerCurrent += writerNode.written;
891
908
  });
909
+ bNothingToSync &&= sourceTenantNode.nothingtosync;
892
910
  bReadingComplete &&= (sourceTenantNode.status == "complete" || sourceTenantNode.status == "failed");
893
911
  readerTotal += sourceTenantNode.total;
894
912
  readerCurrent += sourceTenantNode.read;
@@ -898,37 +916,44 @@ export class BatchArray {
898
916
  setReadersCurrent(readerCurrent);
899
917
  setWritersTotal(Math.max(writerTotal, readerTotal));
900
918
  setWritersCurrent(writerCurrent);
901
- // because it is an important milestone, we always check if we have *just* completed reading
902
- if (bReadingComplete && this.milestoneArray.milestones[0].Read == null) {
903
- this.milestoneArray.read(setMilestones);
904
- setConfigSyncResult("reading complete");
905
- console.log(`Setting config sync result: "reading complete"`);
906
- // trigger refresh delta tokens
907
- setRefreshDeltaTrigger(true);
908
- // change to % per second to complete in 7x as long as it took to get here
909
- let readTS = Date.now();
910
- let secsElapsed = (readTS - this.pb_startTS) / 1000;
911
- let expectedPercentDone = 7;
912
- let expectedPercentPerSecond = secsElapsed / expectedPercentDone;
913
- this.pb_increment = expectedPercentPerSecond;
914
- console.log(`Setting increment: ${this.pb_increment}% per second`);
915
- }
916
- // with that out of the way, is writing complete?
917
- if (bWritingComplete) {
919
+ // check to see if there was nothing to sync
920
+ if (bNothingToSync) {
918
921
  this.milestoneArray.write(setMilestones);
919
- setConfigSyncResult("sync complete");
920
- console.log(`Setting config sync result: "complete"`);
921
- this.pb_progress = 99;
922
+ setConfigSyncResult("nothing to sync");
923
+ console.log(`Setting config sync result: "nothing to sync"`);
922
924
  }
923
- // if not, has writing even started?
924
- else if (bWritingStarted) {
925
- setConfigSyncResult("writing in progress");
926
- console.log(`Setting config sync result: "writing in progress"`);
927
- }
928
- // else, we must be reading (unless we already completed reading)
929
- else if (this.milestoneArray.milestones[0].Read == null){
930
- setConfigSyncResult("reading in progress");
931
- console.log(`Setting config sync result: "reading in progress"`);
925
+ else {
926
+ // because it is an important milestone, we always check if we have *just* completed reading
927
+ if (bReadingComplete && this.milestoneArray.milestones[0].Read == null) {
928
+ this.milestoneArray.read(setMilestones);
929
+ setConfigSyncResult("reading complete");
930
+ console.log(`Setting config sync result: "reading complete"`);
931
+ // trigger refresh delta tokens
932
+ setRefreshDeltaTrigger(true);
933
+ // change to % per second to complete in 7x as long as it took to get here
934
+ let readTS = Date.now();
935
+ let secsElapsed = (readTS - this.pb_startTS) / 1000;
936
+ let expectedPercentDone = 7;
937
+ let expectedPercentPerSecond = secsElapsed / expectedPercentDone;
938
+ this.pb_increment = expectedPercentPerSecond;
939
+ console.log(`Setting increment: ${this.pb_increment}% per second`);
940
+ }
941
+ // with that out of the way, is writing complete?
942
+ if (bWritingComplete) {
943
+ this.milestoneArray.write(setMilestones);
944
+ setConfigSyncResult("sync complete");
945
+ console.log(`Setting config sync result: "complete"`);
946
+ }
947
+ // if not, has writing even started?
948
+ else if (bWritingStarted) {
949
+ setConfigSyncResult("writing in progress");
950
+ console.log(`Setting config sync result: "writing in progress"`);
951
+ }
952
+ // else, we must be reading (unless we already completed reading)
953
+ else if (this.milestoneArray.milestones[0].Read == null) {
954
+ setConfigSyncResult("reading in progress");
955
+ console.log(`Setting config sync result: "reading in progress"`);
956
+ }
932
957
  }
933
958
  }
934
959
  // start SignalR connection based on each batchId
@@ -986,12 +1011,14 @@ export class TenantNode {
986
1011
  read: number;
987
1012
  written: number;
988
1013
  deferred: number;
1014
+ nothingtosync: boolean;
989
1015
  targets: TenantNode[];
990
1016
  constructor(tid: string, name: string, batchId: string) {
991
1017
  this.expanded = false;
992
1018
  this.name = name;
993
1019
  this.tid = tid;
994
1020
  this.batchId = batchId;
1021
+ this.nothingtosync = false;
995
1022
  this.targets = new Array<TenantNode>();
996
1023
  this.update(0, 0, 0, 0);
997
1024
  }
@@ -1047,11 +1074,11 @@ export async function groupsGet(instance: IPublicClientApplication, user: User |
1047
1074
  }
1048
1075
  }
1049
1076
  export function signIn(user: User, tasks: TaskArray): void {
1077
+ // SignIn by an admin consents the full set of permissions, unlike Challenge which requires a consented app
1050
1078
  let tenantURL: string = window.location.href;
1051
- tenantURL += "MicrosoftIdentity/Account/Challenge";
1079
+ tenantURL += "MicrosoftIdentity/Account/SignIn";
1052
1080
  let url: URL = new URL(tenantURL);
1053
1081
  url.searchParams.append("redirectUri", window.location.origin);
1054
- url.searchParams.append("scope", "openid offline_access Directory.AccessAsUser.All CrossTenantInformation.ReadBasic.All");
1055
1082
  url.searchParams.append("domainHint", "organizations");
1056
1083
  if (user.oid !== "1") {
1057
1084
  url.searchParams.append("loginHint", user.mail);
@@ -1072,18 +1099,31 @@ export function signInIncrementally(user: User, scope: string): void {
1072
1099
  url.searchParams.append("loginHint", user.mail);
1073
1100
  window.location.assign(url.href);
1074
1101
  }
1075
- export function signOut(user: User): void {
1102
+ export async function signOut(user: User): Promise<boolean>{
1076
1103
  if (user.oid == "1") return;
1077
- // these lines provide more callbacks during logout
1078
- //let tenantURL: string = window.location.href;
1079
- //tenantURL += "MicrosoftIdentity/Account/SignOut";
1080
- // this line takes advantage of our saved loginHint to logout right away, but requires additional cleanup logic
1081
- // https://aaddevsup.azurewebsites.net/2022/03/how-to-logout-of-an-oauth2-application-without-getting-prompted-to-select-a-user/
1082
- let tenantURL: string = "https://login.microsoftonline.com/common/oauth2/logout";
1083
- let url: URL = new URL(tenantURL);
1084
- url.searchParams.append("post_logout_redirect_uri", window.location.origin);
1085
- url.searchParams.append("logout_hint", user.loginHint);
1086
- window.location.assign(url.href);
1104
+ // set logout_hint in the .NET session for streamlined logout
1105
+ let userEndpoint: string = window.location.href;
1106
+ userEndpoint += "user";
1107
+ let url = new URL(userEndpoint);
1108
+ url.searchParams.append("oid", user.oid);
1109
+ url.searchParams.append("tid", user.tid);
1110
+ url.searchParams.append("loginHint", user.loginHint);
1111
+ url.searchParams.append("verb", "LOGOUT");
1112
+ let options = { method: "PATCH" };
1113
+ let userLogoutResponse: Response = await fetch(url.href, options);
1114
+ if (userLogoutResponse.status == 200 && userLogoutResponse.statusText == "OK") {
1115
+ console.log(`Successfully set admin ${user.mail} logout_hint`);
1116
+ }
1117
+ else {
1118
+ console.log(`Failed to set admin ${user.mail} logout_hint`);
1119
+ return;
1120
+ }
1121
+ // start the logout process triggering callbacks during logout
1122
+ // OnRedirectToIdentityProviderForSignOut - this is where we set the logout_hint for user we are trying to logout
1123
+ // OnSignedOutCallbackRedirect - called when the call sucessfully completes
1124
+ let signoutURL: string = window.location.href;
1125
+ signoutURL += "MicrosoftIdentity/Account/SignOut";
1126
+ window.location.assign(signoutURL);
1087
1127
  }
1088
1128
  //tenantRelationshipsGetByDomain - query AAD for associated company name and id
1089
1129
  export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): Promise<boolean> {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mindline/sync",
3
3
  "type": "module",
4
- "version": "1.0.40",
4
+ "version": "1.0.42",
5
5
  "types": "index.d.ts",
6
6
  "exports": "./index.ts",
7
7
  "description": "sync is a node.js package encapsulating javscript classes required for configuring Mindline sync service.",
package/tasks.ts CHANGED
@@ -26,7 +26,7 @@ const data: any[] = [
26
26
  },
27
27
  {
28
28
  id: 4,
29
- task: "PUT access token",
29
+ task: "PUT tenant",
30
30
  start: "1970-01-01T00:00:00",
31
31
  end: "1970-01-01T00:00:00",
32
32
  expected: "0:01",