@mindline/sync 1.0.48 → 1.0.50

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,6 +2,6 @@
2
2
  "ExpandedNodes": [
3
3
  ""
4
4
  ],
5
- "SelectedNode": "\\index.ts",
5
+ "SelectedNode": "\\package.json",
6
6
  "PreviewInSolutionExplorer": false
7
7
  }
package/.vs/slnx.sqlite CHANGED
Binary file
Binary file
package/hybridspa.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  //hybridspa.ts - calls to Mindline Config API
2
2
  import {
3
- User,
4
- Tenant,
3
+ APIResult,
5
4
  Config,
5
+ graphConfig,
6
+ Tenant,
6
7
  TenantConfigInfo,
7
- APIResult
8
+ User
8
9
  } from "./index";
9
10
  import {
10
11
  IPublicClientApplication,
@@ -12,44 +13,6 @@ import {
12
13
  } from "@azure/msal-browser";
13
14
  import { deserializeArray } from "class-transformer";
14
15
 
15
- // add here endpoints for API services you would like to use.
16
- export const graphConfig = {
17
- // config API endpoints
18
- adminEndpoint:
19
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/admin",
20
- adminIncompleteEndpoint:
21
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/incomplete-admin",
22
- adminsEndpoint:
23
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/admins",
24
- configEndpoint:
25
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/configuration",
26
- configsEndpoint:
27
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/configurations",
28
- initEndpoint:
29
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/configuration/init",
30
- readerStartSyncEndpoint: "https://dev-configurationapi-westus.azurewebsites.net/api/v1/startSync",
31
- tenantEndpoint:
32
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/tenant",
33
- tenantsEndpoint:
34
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/tenants",
35
- workspaceEndpoint:
36
- "https://dev-configurationapi-westus.azurewebsites.net/api/v1/workspaces",
37
- // graph API endpoints
38
- graphGroupsEndpoint: "https://graph.microsoft.com/v1.0/groups",
39
- graphMailEndpoint: "https://graph.microsoft.com/v1.0/me/messages",
40
- graphMeEndpoint: "https://graph.microsoft.com/v1.0/me",
41
- graphUsersEndpoint: "https://graph.microsoft.com/v1.0/users",
42
- // sovereign cloud tenant info endpoints
43
- graphTenantByDomainPredicate: "beta/tenantRelationships/findTenantInformationByDomainName",
44
- graphTenantByIdPredicate: "beta/tenantRelationships/findTenantInformationByTenantId",
45
- // authority values are based on the well-known OIDC auth endpoints
46
- authorityWW: "https://login.microsoftonline.com/",
47
- authorityWWRegex: /^(https:\/\/login\.microsoftonline\.(?:us|com)\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/,
48
- authorityUS: "https://login.microsoftonline.us/",
49
- authorityUSRegex: /^(https:\/\/login\.microsoftonline\.(?:us|com)\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/,
50
- authorityCN: "https://login.partner.microsoftonline.cn/",
51
- authorityCNRegex: /^(https:\/\/login\.partner\.microsoftonline\.cn\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/,
52
- };
53
16
  // helper functions
54
17
  // TODO: this is where you want to trigger a re-authentication if token expires
55
18
  export async function defineHeaders(
@@ -117,12 +80,12 @@ export async function adminDelete(
117
80
  // are we performing deletion of a full admin?
118
81
  let url: URL | null = null;
119
82
  if (user.oid !== user.mail) {
120
- url = new URL(graphConfig.adminEndpoint);
83
+ url = new URL(graphConfig.adminEndpoint());
121
84
  url.searchParams.append("workspaceId", workspaceId);
122
85
  }
123
86
  // or of an incomplete admin?
124
87
  else if (user.mail !== "") {
125
- url = new URL(graphConfig.adminIncompleteEndpoint);
88
+ url = new URL(graphConfig.adminIncompleteEndpoint());
126
89
  url.searchParams.append("email", user.mail);
127
90
  url.searchParams.append("workspaceId", workspaceId);
128
91
  }
@@ -168,9 +131,9 @@ export async function adminsGet(
168
131
  return result;
169
132
  }
170
133
  // create endpoint
171
- let adminsEndpoint: string = graphConfig.adminsEndpoint;
134
+ let endpoint: string = graphConfig.adminsEndpoint();
172
135
  // add parameter to endpoint
173
- let url: URL = new URL(adminsEndpoint);
136
+ let url: URL = new URL(endpoint);
174
137
  url.searchParams.append("workspaceId", workspaceID);
175
138
  // create headers
176
139
  const headers = await defineHeaders(instance, authorizedUser);
@@ -228,7 +191,7 @@ export async function adminPost(
228
191
  return result;
229
192
  }
230
193
  // create admin endpoint
231
- let adminEndpoint: string = graphConfig.adminEndpoint;
194
+ let endpoint: string = graphConfig.adminEndpoint();
232
195
  // create headers
233
196
  const headers = await defineHeaders(instance, authorizedUser);
234
197
  // create admin body
@@ -240,8 +203,8 @@ export async function adminPost(
240
203
  let options = { method: "POST", headers: headers, body: adminBody };
241
204
  // make admin endpoint call
242
205
  try {
243
- console.log("Attempting POST to /admin: " + adminEndpoint);
244
- let response = await fetch(adminEndpoint, options);
206
+ console.log("Attempting POST to /admin: " + endpoint);
207
+ let response = await fetch(endpoint, options);
245
208
  if (response.status === 200 && response.statusText === "OK") {
246
209
  console.log(`Successful POST to /admin: ${adminBody}`);
247
210
  return result;
@@ -277,7 +240,7 @@ export async function configDelete(
277
240
  return result;
278
241
  }
279
242
  let url: URL | null = null;
280
- url = new URL(graphConfig.configEndpoint);
243
+ url = new URL(graphConfig.configEndpoint());
281
244
  url.searchParams.append("configurationId", config.id);
282
245
  // create headers
283
246
  const headers = await defineHeaders(instance, authorizedUser);
@@ -323,7 +286,7 @@ export async function configPost(
323
286
  return result;
324
287
  }
325
288
  // create no parameter config endpoint
326
- let configEndpoint: string = graphConfig.configEndpoint;
289
+ let endpoint: string = graphConfig.configEndpoint();
327
290
  // create config headers
328
291
  const headers = await defineHeaders(instance, authorizedUser);
329
292
  // create config body
@@ -357,8 +320,8 @@ export async function configPost(
357
320
  // make config endpoint call
358
321
  try {
359
322
  if (debug) debugger;
360
- console.log("Attempting POST to /config: " + configEndpoint);
361
- let response = await fetch(configEndpoint, options);
323
+ console.log("Attempting POST to /config: " + endpoint);
324
+ let response = await fetch(endpoint, options);
362
325
  if (response.status === 200 && response.statusText === "OK") {
363
326
  let data = await response.json();
364
327
  config.id = data;
@@ -399,8 +362,8 @@ export async function configPut(
399
362
  return result;
400
363
  }
401
364
  // create parametrized config endpoint
402
- let configEndpoint: string = graphConfig.configEndpoint;
403
- let url: URL = new URL(configEndpoint);
365
+ let endpoint: string = graphConfig.configEndpoint();
366
+ let url: URL = new URL(endpoint);
404
367
  url.searchParams.append("configurationId", config.id);
405
368
  // create config headers
406
369
  const headers = await defineHeaders(instance, authorizedUser);
@@ -474,9 +437,9 @@ export async function configsGet(
474
437
  return result;
475
438
  }
476
439
  // create endpoint
477
- let configsEndpoint: string = graphConfig.configsEndpoint;
440
+ let endpoint: string = graphConfig.configsEndpoint();
478
441
  // add parameter to endpoint
479
- let url: URL = new URL(configsEndpoint);
442
+ let url: URL = new URL(endpoint);
480
443
  url.searchParams.append("workspaceId", workspaceID);
481
444
  // create headers
482
445
  const headers = await defineHeaders(instance, authorizedUser);
@@ -535,7 +498,7 @@ export async function initPost(
535
498
  return result;
536
499
  }
537
500
  // create init endpoint
538
- let initEndpoint: string = graphConfig.initEndpoint;
501
+ let endpoint: string = graphConfig.initEndpoint();
539
502
  // create init headers
540
503
  const headers = await defineHeaders(instance, authorizedUser);
541
504
  // create init body
@@ -553,8 +516,8 @@ export async function initPost(
553
516
  // make init endpoint call
554
517
  try {
555
518
  if (debug) debugger;
556
- console.log("Attempting POST to /configuration/init: " + initEndpoint);
557
- let response = await fetch(initEndpoint, options);
519
+ console.log("Attempting POST to /configuration/init: " + endpoint);
520
+ let response = await fetch(endpoint, options);
558
521
  if (response.status === 200 && response.statusText === "OK") {
559
522
  console.log(`Successful POST to /configuration/init: ${initBody}`);
560
523
  return result;
@@ -593,7 +556,7 @@ export async function tenantDelete(
593
556
  return result;
594
557
  }
595
558
  // create parametrized tenant endpoint
596
- let url: URL = new URL(graphConfig.tenantEndpoint);
559
+ let url: URL = new URL(graphConfig.tenantEndpoint());
597
560
  url.searchParams.append("tenantId", tenant.tid);
598
561
  url.searchParams.append("workspaceId", workspaceId);
599
562
  // create headers
@@ -640,9 +603,9 @@ export async function tenantsGet(
640
603
  return result;
641
604
  }
642
605
  // create endpoint
643
- let tenantsEndpoint: string = graphConfig.tenantsEndpoint;
606
+ let endpoint: string = graphConfig.tenantsEndpoint();
644
607
  // add parameter to endpoint
645
- let url: URL = new URL(tenantsEndpoint);
608
+ let url: URL = new URL(endpoint);
646
609
  url.searchParams.append("workspaceId", workspaceID);
647
610
  // create headers
648
611
  const headers = await defineHeaders(instance, authorizedUser);
@@ -701,8 +664,8 @@ export async function tenantPost(
701
664
  return result;
702
665
  }
703
666
  // create parametrized tenant endpoint
704
- let tenantEndpoint: string = graphConfig.tenantEndpoint;
705
- let url: URL = new URL(tenantEndpoint);
667
+ let endpoint: string = graphConfig.tenantEndpoint();
668
+ let url: URL = new URL(endpoint);
706
669
  url.searchParams.append("workspaceId", workspaceId);
707
670
  // create tenant headers
708
671
  const headers = await defineHeaders(instance, addingUser);
@@ -756,7 +719,7 @@ export async function tenantPut(
756
719
  return result;
757
720
  }
758
721
  // create tenant endpoint
759
- let tenantEndpoint: string = graphConfig.tenantEndpoint;
722
+ let endpoint: string = graphConfig.tenantEndpoint();
760
723
  // create tenant headers
761
724
  const headers = await defineHeaders(instance, authorizedUser);
762
725
  // establish read and write service principals ("notassigned" is default")
@@ -782,14 +745,14 @@ export async function tenantPut(
782
745
  // make tenant endpoint call
783
746
  try {
784
747
  if (debug) debugger;
785
- console.log(`Attempting PUT to ${tenantEndpoint}`);
786
- let response = await fetch(tenantEndpoint, options);
748
+ console.log(`Attempting PUT to ${endpoint}`);
749
+ let response = await fetch(endpoint, options);
787
750
  if (response.status === 200 && response.statusText === "OK") {
788
- console.log(`Successful PUT to ${tenantEndpoint}`);
751
+ console.log(`Successful PUT to ${endpoint}`);
789
752
  return result;
790
753
  }
791
754
  else {
792
- console.log(`Failed PUT to ${tenantEndpoint}: ${tenantBody}`);
755
+ console.log(`Failed PUT to ${endpoint}: ${tenantBody}`);
793
756
  result.error = await processErrors(response);
794
757
  console.log(result.error);
795
758
  result.status = 500;
@@ -821,9 +784,9 @@ export async function workspacesGet(
821
784
  return result;
822
785
  }
823
786
  // create workspaces endpoint
824
- let workspaceEndpoint: string = graphConfig.workspaceEndpoint;
787
+ let endpoint: string = graphConfig.workspaceEndpoint();
825
788
  // create workspace endpoint
826
- let url: URL = new URL(workspaceEndpoint);
789
+ let url: URL = new URL(endpoint);
827
790
  // create workspace headers
828
791
  const headers = await defineHeaders(instance, authorizedUser);
829
792
  // make workspace endpoint call
@@ -880,7 +843,7 @@ export async function readerPost(
880
843
  return result;
881
844
  }
882
845
  // create reader endpoint
883
- let readerEndpoint: string = graphConfig.readerStartSyncEndpoint;
846
+ let readerEndpoint: string = graphConfig.readerStartSyncEndpoint();
884
847
  let url: URL = new URL(readerEndpoint);
885
848
  url.searchParams.append("configurationId", config.id);
886
849
  // create headers
@@ -893,7 +856,14 @@ export async function readerPost(
893
856
  if (response.status === 200 && response.statusText === "OK") {
894
857
  console.log(`Successful POST to /startSync: ${readerEndpoint}`);
895
858
  let jsonResponse = await response.json();
896
- result.array = JSON.parse(jsonResponse.PayloadStr);
859
+ if (jsonResponse.PayloadStr != "") {
860
+ result.array = JSON.parse(jsonResponse.PayloadStr);
861
+ }
862
+ else {
863
+ result.result = false;
864
+ result.error = "readerPost: blank payload returned, sync may be disabled on back end";
865
+ result.status = 500;
866
+ }
897
867
  return result;
898
868
  } else {
899
869
  result.error = await processErrors(response);
package/index.d.ts CHANGED
@@ -3,6 +3,37 @@ declare module "@mindline/sync" {
3
3
  export function sum(a: number, b: number): number;
4
4
  export function helloNpm(): string;
5
5
 
6
+ export class graphConfig {
7
+ static environmentTag: string = "dev";
8
+ // config API endpoints
9
+ static adminEndpoint(): string;
10
+ static adminIncompleteEndpoint(): string;
11
+ static adminsEndpoint(): string;
12
+ static configEndpoint(): string;
13
+ static configsEndpoint(): string;
14
+ static initEndpoint(): string;
15
+ static readerStartSyncEndpoint(): string;
16
+ static tenantEndpoint(): string;
17
+ static tenantsEndpoint(): string;
18
+ static workspaceEndpoint(): string;
19
+ // SignalR endpoint
20
+ static signalREndpoint(): string;
21
+ // graph API endpoints
22
+ static graphGroupsEndpoint: string = "https://graph.microsoft.com/v1.0/groups";
23
+ static graphMailEndpoint: string = "https://graph.microsoft.com/v1.0/me/messages";
24
+ static graphMeEndpoint: string = "https://graph.microsoft.com/v1.0/me";
25
+ static graphUsersEndpoint: string = "https://graph.microsoft.com/v1.0/users";
26
+ // sovereign cloud tenant info endpoints
27
+ static graphTenantByDomainPredicate: string = "beta/tenantRelationships/findTenantInformationByDomainName";
28
+ static graphTenantByIdPredicate: string = "beta/tenantRelationships/findTenantInformationByTenantId";
29
+ // authority values are based on the well-known OIDC auth endpoints
30
+ static authorityWW: string = "https://login.microsoftonline.com/";
31
+ static authorityWWRegex: RegExp = /^(https:\/\/login\.microsoftonline\.(?:us|com)\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/;
32
+ static authorityUS: string = "https://login.microsoftonline.us/";
33
+ static authorityUSRegex: RegExp = /^(https:\/\/login\.microsoftonline\.(?:us|com)\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/;
34
+ static authorityCN: string = "https://login.partner.microsoftonline.cn/";
35
+ static authorityCNRegex: RegExp = /^(https:\/\/login\.partner\.microsoftonline\.cn\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/;
36
+ };
6
37
  export class Group {
7
38
  id: string;
8
39
  displayName: string;
@@ -116,7 +147,6 @@ declare module "@mindline/sync" {
116
147
  export type TaskType = "initialization" |
117
148
  "authenticate user" |
118
149
  "reload React" |
119
- "PUT tenant" |
120
150
  "GET tenant details" |
121
151
  "POST config init" |
122
152
  "GET workspaces" |
@@ -233,6 +263,7 @@ declare module "@mindline/sync" {
233
263
  export function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{groups: Group[], error: string}>;
234
264
  export function signIn(user: User, tasks: TaskArray): void;
235
265
  export function signInIncrementally(user: User, scope: string): void;
266
+ export function requestAdminConsent(user: User, scope: string): void;
236
267
  export function signOut(user: User): boolean;
237
268
  export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
238
269
  export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
package/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  //index.ts - published interface - AAD implementations, facade to Mindline Config API
2
2
  import * as signalR from "@microsoft/signalr"
3
3
  import { IPublicClientApplication, AuthenticationResult } from "@azure/msal-browser"
4
- import { deserializeArray, instanceToPlain, ClassTransformOptions } from 'class-transformer';
5
- import { defineHeaders, adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet } from './hybridspa';
4
+ import { deserializeArray } from 'class-transformer';
5
+ import { defineHeaders, adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet } from './hybridspa';
6
6
  import { version } from './package.json';
7
7
  import users from "./users.json";
8
8
  import tenants from "./tenants.json";
@@ -19,6 +19,60 @@ export function sum(a: number, b: number): number {
19
19
  export function helloNpm(): string {
20
20
  return "hello NPM";
21
21
  }
22
+ // main application exports
23
+ export class graphConfig {
24
+ static environmentTag: string = "dev";
25
+ // config API endpoints
26
+ static adminEndpoint(): string {
27
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/admin`
28
+ };
29
+ static adminIncompleteEndpoint(): string {
30
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/incomplete-admin`;
31
+ };
32
+ static adminsEndpoint(): string {
33
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/admins`;
34
+ };
35
+ static configEndpoint(): string {
36
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/configuration`;
37
+ };
38
+ static configsEndpoint(): string {
39
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/configurations`;
40
+ };
41
+ static initEndpoint(): string {
42
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/configuration/init`;
43
+ };
44
+ static readerStartSyncEndpoint(): string {
45
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/startSync`;
46
+ };
47
+ static tenantEndpoint(): string {
48
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/tenant`;
49
+ };
50
+ static tenantsEndpoint(): string {
51
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/tenants`;
52
+ };
53
+ static workspaceEndpoint(): string {
54
+ return `https://${graphConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/workspaces`;
55
+ };
56
+ // SignalR endpoint
57
+ static signalREndpoint(): string {
58
+ return `https://${graphConfig.environmentTag}-signalrdispatcher-westus.azurewebsites.net/statsHub`;
59
+ };
60
+ // graph API endpoints
61
+ static graphGroupsEndpoint: string = "https://graph.microsoft.com/v1.0/groups";
62
+ static graphMailEndpoint: string = "https://graph.microsoft.com/v1.0/me/messages";
63
+ static graphMeEndpoint: string = "https://graph.microsoft.com/v1.0/me";
64
+ static graphUsersEndpoint: string = "https://graph.microsoft.com/v1.0/users";
65
+ // sovereign cloud tenant info endpoints
66
+ static graphTenantByDomainPredicate: string = "beta/tenantRelationships/findTenantInformationByDomainName";
67
+ static graphTenantByIdPredicate: string = "beta/tenantRelationships/findTenantInformationByTenantId";
68
+ // authority values are based on the well-known OIDC auth endpoints
69
+ static authorityWW: string = "https://login.microsoftonline.com/";
70
+ static authorityWWRegex: RegExp = /^(https:\/\/login\.microsoftonline\.(?:us|com)\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/;
71
+ static authorityUS: string = "https://login.microsoftonline.us/";
72
+ static authorityUSRegex: RegExp = /^(https:\/\/login\.microsoftonline\.(?:us|com)\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/;
73
+ static authorityCN: string = "https://login.partner.microsoftonline.cn/";
74
+ static authorityCNRegex: RegExp = /^(https:\/\/login\.partner\.microsoftonline\.cn\/)([\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{4}-[\dA-Fa-f]{12})\/oauth2\/authorize$/;
75
+ };
22
76
  export class Group {
23
77
  id: string;
24
78
  displayName: string;
@@ -391,7 +445,6 @@ export class InitInfo {
391
445
  export type TaskType = "initialization" |
392
446
  "authenticate user" |
393
447
  "reload React" |
394
- "PUT tenant" |
395
448
  "GET tenant details" |
396
449
  "POST config init" |
397
450
  "GET workspaces" |
@@ -819,10 +872,10 @@ export class BatchArray {
819
872
  initializeProgressBar(setSyncProgress: (progress: number) => void, setConfigSyncResult: (result: string) => void, setIdleText: (idleText: string) => void, setMilestones: (milestones: Milestone[]) => void): void {
820
873
  this.pb_startTS = Date.now();
821
874
  this.pb_progress = 0;
822
- this.pb_increment = 1;
875
+ this.pb_increment = .25;
823
876
  this.pb_idle = 0;
824
877
  this.pb_idleMax = 0;
825
- setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
878
+ setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}/60]`);
826
879
  this.pb_timer = setInterval(() => {
827
880
  // if signalR has finished the sync, stop the timer
828
881
  if (this.milestoneArray.milestones[0].Write != null) {
@@ -833,10 +886,10 @@ export class BatchArray {
833
886
  setIdleText(`Complete. [max idle: ${this.pb_idleMax}]`);
834
887
  }
835
888
  else {
836
- // if we've gone 30 seconds without a signalR message, finish the sync
889
+ // if we've gone 60 seconds without a signalR message, finish the sync
837
890
  this.pb_idle = this.pb_idle + 1;
838
891
  this.pb_idleMax = Math.max(this.pb_idle, this.pb_idleMax);
839
- setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
892
+ setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}/60]`);
840
893
  if (this.pb_idle >= 60) {
841
894
  clearInterval(this.pb_timer);
842
895
  this.pb_timer = null;
@@ -916,11 +969,13 @@ export class BatchArray {
916
969
  let bTotalCount = statskeys[j].endsWith("TotalCount");
917
970
  let bCurrentCount = statskeys[j].endsWith("CurrentCount");
918
971
  let bDeferredCount = statskeys[j].endsWith("DeferredCount");
972
+ let bRescheduledCount = statskeys[j].endsWith("RescheduledCount");
919
973
  if (statskeys[j].startsWith("Reader")) {
920
974
  // parse tid from Reader key
921
975
  let tidRegexp = /Reader\/TID:(.+)\/TotalCount/;
922
976
  if (bCurrentCount) tidRegexp = /Reader\/TID:(.+)\/CurrentCount/;
923
977
  if (bDeferredCount) tidRegexp = /Reader\/TID:(.+)\/DeferredCount/;
978
+ if (bRescheduledCount) tidRegexp = /Reader\/TID:(.+)\/RescheduledCount/;
924
979
  let matchTID = statskeys[j].match(tidRegexp);
925
980
  if (matchTID == null) {
926
981
  console.log(`tid not found in ${statskeys[j]}.`);
@@ -945,6 +1000,7 @@ export class BatchArray {
945
1000
  let tidRegexp = /Writer\/TID:(.+)\/TotalCount/;
946
1001
  if (bCurrentCount) tidRegexp = /Writer\/TID:(.+)\/CurrentCount/;
947
1002
  if (bDeferredCount) tidRegexp = /Writer\/TID:(.+)\/DeferredCount/;
1003
+ if (bRescheduledCount) tidRegexp = /Writer\/TID:(.+)\/RescheduledCount/;
948
1004
  let matchTID = statskeys[j].match(tidRegexp);
949
1005
  if (matchTID == null) {
950
1006
  console.log(`tid not found in ${statskeys[j]}.`);
@@ -968,7 +1024,7 @@ export class BatchArray {
968
1024
  writerNode.written = Math.max(Number(statsvalues[j]), writerNode.written);
969
1025
  console.log(`----- ${writerNode.name} Total Written: ${writerNode.written}`);
970
1026
  }
971
- else if (bDeferredCount) {
1027
+ else if (bDeferredCount || bRescheduledCount) {
972
1028
  writerNode.deferred = Math.max(Number(statsvalues[j]), writerNode.deferred);
973
1029
  console.log(`----- ${writerNode.name} Total Deferred: ${writerNode.deferred}`);
974
1030
  }
@@ -1022,10 +1078,10 @@ export class BatchArray {
1022
1078
  console.log(`Setting config sync result: "reading complete"`);
1023
1079
  // trigger refresh delta tokens
1024
1080
  setRefreshDeltaTrigger(true);
1025
- // change to % per second to complete in 7x as long as it took to get here
1081
+ // change to % per second to complete in 12x as long as it took to get here
1026
1082
  let readTS = Date.now();
1027
1083
  let secsElapsed = (readTS - this.pb_startTS) / 1000;
1028
- let expectedPercentDone = 7;
1084
+ let expectedPercentDone = 8.5;
1029
1085
  let expectedPercentPerSecond = secsElapsed / expectedPercentDone;
1030
1086
  this.pb_increment = expectedPercentPerSecond;
1031
1087
  console.log(`Setting increment: ${this.pb_increment}% per second`);
@@ -1050,10 +1106,12 @@ export class BatchArray {
1050
1106
  }
1051
1107
  // start SignalR connection based on each batchId
1052
1108
  batchIdArray.map((batchPair: Object) => {
1053
- const endpointUrl = `https://dev-signalrdispatcher-westus.azurewebsites.net/statsHub?statsId=${batchPair.BatchId}`;
1054
- console.log(`Creating SignalR Hub for TID: ${batchPair.SourceId} ${endpointUrl}`);
1109
+ const endpoint: string = graphConfig.signalREndpoint();
1110
+ let endpointUrl: URL = new URL(endpoint);
1111
+ endpointUrl.searchParams.append("statsId", batchPair.BatchId);
1112
+ console.log(`Creating SignalR Hub for TID: ${batchPair.SourceId} ${endpointUrl.href}`);
1055
1113
  const connection: signalR.HubConnection = new signalR.HubConnectionBuilder()
1056
- .withUrl(endpointUrl)
1114
+ .withUrl(endpointUrl.href)
1057
1115
  .withAutomaticReconnect()
1058
1116
  .configureLogging(signalR.LogLevel.Information)
1059
1117
  .build();
@@ -1181,6 +1239,9 @@ export function signIn(user: User, tasks: TaskArray): void {
1181
1239
  }
1182
1240
  export function signInIncrementally(user: User, scope: string): void {
1183
1241
  if (user.oid == "1") return;
1242
+ //
1243
+ // for delegated permissions, we can use the Microsoft Identity Web Account Controller Challenge method
1244
+ //
1184
1245
  let tenantURL: string = window.location.href;
1185
1246
  tenantURL += "MicrosoftIdentity/Account/Challenge";
1186
1247
  let url: URL = new URL(tenantURL);
@@ -1190,6 +1251,31 @@ export function signInIncrementally(user: User, scope: string): void {
1190
1251
  url.searchParams.append("loginHint", user.mail);
1191
1252
  window.location.assign(url.href);
1192
1253
  }
1254
+ export function requestAdminConsent(user: User, scope: string): void {
1255
+ if (user.oid == "1") return;
1256
+ //
1257
+ // for app permissions (app roles) we must use the /.default scope for admin consent
1258
+ // https://learn.microsoft.com/EN-US/azure/active-directory/develop/v2-admin-consent#:~:text=In%20order%20to%20request%20app%20permissions%2C%20you%20must%20use%20the%20/.default%20value.
1259
+ // https://learn.microsoft.com/en-us/answers/questions/431784/how-to-grant-application-permissions-with-dynamic
1260
+ //
1261
+ // this means that, if we want to be granular about SyncReader vs. SyncWriter permissions, we must have separate Applications
1262
+ // for now, we request the /.default scope whenever any of the SyncReader or SyncWriter permissions are requested
1263
+ //
1264
+ // trying to use the Challenge endpoint for app permissions, setting this scope caused the call to quietly fail without error
1265
+ // scope = "63100afe-506e-4bb2-8ff7-d8d5ab373129/.default";
1266
+ //
1267
+ // thereforce, we are assuming that, for app permissions (app roles), the Microsoft Identity Web Challenge endpoint does not work and we need to perform our own redirect to the admin consent endpoint
1268
+ // https://learn.microsoft.com/EN-US/azure/active-directory/develop/scopes-oidc#client-credentials-grant-flow-and-default
1269
+ // https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#request-the-permissions-from-a-directory-admin
1270
+ //
1271
+ let adminConsentURL: string = "https://login.microsoftonline.com/";
1272
+ adminConsentURL += user.tid;
1273
+ adminConsentURL += "/adminconsent";
1274
+ let url: URL = new URL(adminConsentURL);
1275
+ url.searchParams.append("client_id", "63100afe-506e-4bb2-8ff7-d8d5ab373129");
1276
+ url.searchParams.append("redirectUri", window.location.origin);
1277
+ window.location.assign(url.href);
1278
+ }
1193
1279
  export async function signOut(user: User): Promise<boolean>{
1194
1280
  if (user.oid == "1") return;
1195
1281
  // set logout_hint in the .NET session for streamlined logout
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mindline/sync",
3
3
  "type": "module",
4
- "version": "1.0.48",
4
+ "version": "1.0.50",
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,14 +26,6 @@ const data: any[] = [
26
26
  },
27
27
  {
28
28
  id: 4,
29
- task: "PUT tenant",
30
- start: "1970-01-01T00:00:00",
31
- end: "1970-01-01T00:00:00",
32
- expected: "0:01",
33
- status: "not started"
34
- },
35
- {
36
- id: 5,
37
29
  task: "GET tenant details",
38
30
  start: "1970-01-01T00:00:00",
39
31
  end: "1970-01-01T00:00:00",
@@ -41,7 +33,7 @@ const data: any[] = [
41
33
  status: "not started"
42
34
  },
43
35
  {
44
- id: 6,
36
+ id: 5,
45
37
  task: "POST config init",
46
38
  start: "1970-01-01T00:00:00",
47
39
  end: "1970-01-01T00:00:00",
@@ -49,7 +41,7 @@ const data: any[] = [
49
41
  status: "not started"
50
42
  },
51
43
  {
52
- id: 7,
44
+ id: 6,
53
45
  task: "GET workspaces",
54
46
  start: "1970-01-01T00:00:00",
55
47
  end: "1970-01-01T00:00:00",