@mindline/sync 1.0.39 → 1.0.41

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,5 @@
2
2
  "ExpandedNodes": [
3
3
  ""
4
4
  ],
5
- "SelectedNode": "\\index.ts",
6
5
  "PreviewInSolutionExplorer": false
7
6
  }
package/.vs/slnx.sqlite CHANGED
Binary file
Binary file
package/hybridspa.ts CHANGED
@@ -51,7 +51,8 @@ export const graphConfig = {
51
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
52
  };
53
53
  // helper functions
54
- async function defineHeaders(
54
+ // TODO: this is where you want to trigger a re-authentication if token expires
55
+ export async function defineHeaders(
55
56
  instance: IPublicClientApplication,
56
57
  user: User
57
58
  ): Promise<Headers> {
@@ -758,16 +759,8 @@ export async function tenantPut(
758
759
  let tenantEndpoint: string = graphConfig.tenantEndpoint;
759
760
  // create tenant headers
760
761
  const headers = await defineHeaders(instance, authorizedUser);
761
- // be sure we send null and not "null" in body
762
- let readAccessToken: string = tenant.readServicePrincipal
763
- ? `"${tenant.readServicePrincipal}"`
764
- : "null";
765
- let writeAccessToken: string = tenant.writeServicePrincipal
766
- ? `"${tenant.writeServicePrincipal}"`
767
- : "null";
768
762
  // create tenant body
769
- let tenantBody: string = `
770
- {"tenantId": "${tenant.tid}", "readServicePrincipal": ${readAccessToken}, "writeServicePrincipal": ${writeAccessToken}}`;
763
+ let tenantBody: string = `{"tenantId": "${tenant.tid}"}`;
771
764
  let options = { method: "PUT", headers: headers, body: tenantBody };
772
765
  // make tenant endpoint call
773
766
  try {
package/index.d.ts CHANGED
@@ -50,8 +50,6 @@ declare module "@mindline/sync" {
50
50
  permissionType: TenantPermissionTypeStrings; // read/write/notassigned
51
51
  onboarded: string; // have we onboarded this tenant? "true" or "false"
52
52
  authority: string; // from AAD ID auth response
53
- readServicePrincipal: string; // from AAD consent
54
- writeServicePrincipal: string; // from AAD consent
55
53
  workspaceIDs: string;
56
54
  constructor();
57
55
  }
@@ -205,6 +203,7 @@ declare module "@mindline/sync" {
205
203
  read: number;
206
204
  written: number;
207
205
  deferred: number;
206
+ nothingtosync: boolean;
208
207
  targets: TenantNode[];
209
208
  constructor(tid: string, name: string);
210
209
  update(total: number, read: number, written: number, deferred: number): void;
@@ -219,15 +218,14 @@ declare module "@mindline/sync" {
219
218
  //
220
219
  // Azure AD Graph API
221
220
  //
222
- export function groupGet(tenant: Tenant, groupid: string): Promise<{group: string, error: string}>;
223
- export function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{groups: Group[], error: string}>;
221
+ export function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{groups: Group[], error: string}>;
224
222
  export function signIn(user: User, tasks: TaskArray): void;
225
223
  export function signInIncrementally(user: User, scope: string): void;
226
224
  export function signOut(user: User): void;
227
225
  export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
228
226
  export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
229
227
  export function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): Promise<boolean>;
230
- export function usersGet(tenant: Tenant): { users: string[], error: string };
228
+ export function usersGet(instance: IPublicClientApplication, user: User | undefined): { users: string[], error: string };
231
229
  //
232
230
  // Mindline Config API
233
231
  //
package/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import * as signalR from "@microsoft/signalr"
3
3
  import { IPublicClientApplication, AuthenticationResult } from "@azure/msal-browser"
4
4
  import { deserializeArray, instanceToPlain, ClassTransformOptions } from 'class-transformer';
5
- import { adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet } from './hybridspa';
5
+ import { defineHeaders, adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, 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";
@@ -79,8 +79,6 @@ export class Tenant {
79
79
  permissionType: TenantPermissionTypeStrings;
80
80
  onboarded: string;
81
81
  authority: string;
82
- readServicePrincipal: string;
83
- writeServicePrincipal: string;
84
82
  workspaceIDs: string;
85
83
  constructor() {
86
84
  this.tid = "";
@@ -90,8 +88,6 @@ export class Tenant {
90
88
  this.permissionType = "notassigned";
91
89
  this.onboarded = "false";
92
90
  this.authority = "";
93
- this.readServicePrincipal = "";
94
- this.writeServicePrincipal = "";
95
91
  this.workspaceIDs = "";
96
92
  }
97
93
  }
@@ -736,22 +732,32 @@ export class BatchArray {
736
732
  this.pb_idleMax = 0;
737
733
  setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
738
734
  this.pb_timer = setInterval(() => {
739
- // if we go 20 seconds without a signalR message, finish the sync
740
- this.pb_idle = this.pb_idle + 1;
741
- this.pb_idleMax = Math.max(this.pb_idle, this.pb_idleMax);
742
- setIdleText(`No updates seen for ${this.pb_idle} seconds. [max idle: ${this.pb_idleMax}]`);
743
- if (this.pb_idle >= 20) {
735
+ // if signalR has finished the sync, stop the timer
736
+ if (this.milestoneArray.milestones[0].Write != null) {
744
737
  clearInterval(this.pb_timer);
745
738
  this.pb_timer = null;
746
- if (this.milestoneArray.milestones[0].Write == null) {
747
- this.milestoneArray.write(setMilestones);
748
- }
749
- setConfigSyncResult(`finished sync, no updates for ${this.pb_idle} seconds`);
750
- }
751
- // if we get to 100, stop the timer, let SignalR or countdown timer finish sync
752
- if (this.pb_progress < 100) {
753
- this.pb_progress = Math.min(100, this.pb_progress + this.pb_increment);
739
+ this.pb_progress = 100;
754
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
+ }
755
761
  }
756
762
  }, 1000);
757
763
  this.milestoneArray.start(setMilestones);
@@ -807,10 +813,13 @@ export class BatchArray {
807
813
  return;
808
814
  }
809
815
  tenantNode.batchId = matchingPair.BatchId;
810
- // process stats for this SignalR message batch
816
+ // process stats for this SignalR message (one batch per tenant node)
811
817
  let statsarray = item.Stats; // get the array of statistics
812
818
  let statskeys = Object.keys(statsarray); // get the keys of the array
813
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;
814
823
  for (let j = 0; j < statskeys.length; j++) {
815
824
  let bTotalCount = statskeys[j].endsWith("TotalCount");
816
825
  let bCurrentCount = statskeys[j].endsWith("CurrentCount");
@@ -827,15 +836,18 @@ export class BatchArray {
827
836
  return;
828
837
  }
829
838
  if (bTotalCount) {
839
+ bTotalCountZero = Number(statsvalues[j]) == 0;
830
840
  tenantNode.total = Math.max(Number(statsvalues[j]), tenantNode.total);
831
841
  console.log(`----- ${tenantNode.name} TID: ${tenantNode.tid} batchId: ${tenantNode.batchId}`);
832
842
  console.log(`----- ${tenantNode.name} Total To Read: ${tenantNode.total}`);
833
843
  }
834
844
  else {
845
+ bCurrentCountZero = Number(statsvalues[j]) == 0;
835
846
  tenantNode.read = Math.max(Number(statsvalues[j]), tenantNode.read);
836
847
  console.log(`----- ${tenantNode.name} Currently Read: ${tenantNode.read}`);
837
848
  }
838
849
  }
850
+ tenantNode.nothingtosync = bTotalCountZero && bCurrentCountZero;
839
851
  if (statskeys[j].startsWith("Writer")) {
840
852
  // parse tid from Writer key
841
853
  let tidRegexp = /Writer\/TID:(.+)\/TotalCount/;
@@ -882,6 +894,7 @@ export class BatchArray {
882
894
  let bReadingComplete: boolean = true;
883
895
  let bWritingComplete: boolean = true;
884
896
  let bWritingStarted: boolean = false;
897
+ let bNothingToSync: boolean = true;
885
898
  let readerTotal: number = 0;
886
899
  let readerCurrent: number = 0;
887
900
  let writerTotal: number = 0;
@@ -893,6 +906,7 @@ export class BatchArray {
893
906
  writerTotal += Math.max(writerNode.total, sourceTenantNode.total);
894
907
  writerCurrent += writerNode.written;
895
908
  });
909
+ bNothingToSync &&= sourceTenantNode.nothingtosync;
896
910
  bReadingComplete &&= (sourceTenantNode.status == "complete" || sourceTenantNode.status == "failed");
897
911
  readerTotal += sourceTenantNode.total;
898
912
  readerCurrent += sourceTenantNode.read;
@@ -902,37 +916,44 @@ export class BatchArray {
902
916
  setReadersCurrent(readerCurrent);
903
917
  setWritersTotal(Math.max(writerTotal, readerTotal));
904
918
  setWritersCurrent(writerCurrent);
905
- // because it is an important milestone, we always check if we have *just* completed reading
906
- if (bReadingComplete && this.milestoneArray.milestones[0].Read == null) {
907
- this.milestoneArray.read(setMilestones);
908
- setConfigSyncResult("reading complete");
909
- console.log(`Setting config sync result: "reading complete"`);
910
- // trigger refresh delta tokens
911
- setRefreshDeltaTrigger(true);
912
- // change to % per second to complete in 7x as long as it took to get here
913
- let readTS = Date.now();
914
- let secsElapsed = (readTS - this.pb_startTS) / 1000;
915
- let expectedPercentDone = 7;
916
- let expectedPercentPerSecond = secsElapsed / expectedPercentDone;
917
- this.pb_increment = expectedPercentPerSecond;
918
- console.log(`Setting increment: ${this.pb_increment}% per second`);
919
- }
920
- // with that out of the way, is writing complete?
921
- if (bWritingComplete) {
919
+ // check to see if there was nothing to sync
920
+ if (bNothingToSync) {
922
921
  this.milestoneArray.write(setMilestones);
923
- setConfigSyncResult("sync complete");
924
- console.log(`Setting config sync result: "complete"`);
925
- this.pb_progress = 99;
926
- }
927
- // if not, has writing even started?
928
- else if (bWritingStarted) {
929
- setConfigSyncResult("writing in progress");
930
- console.log(`Setting config sync result: "writing in progress"`);
922
+ setConfigSyncResult("nothing to sync");
923
+ console.log(`Setting config sync result: "nothing to sync"`);
931
924
  }
932
- // else, we must be reading (unless we already completed reading)
933
- else if (this.milestoneArray.milestones[0].Read == null){
934
- setConfigSyncResult("reading in progress");
935
- 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
+ }
936
957
  }
937
958
  }
938
959
  // start SignalR connection based on each batchId
@@ -990,12 +1011,14 @@ export class TenantNode {
990
1011
  read: number;
991
1012
  written: number;
992
1013
  deferred: number;
1014
+ nothingtosync: boolean;
993
1015
  targets: TenantNode[];
994
1016
  constructor(tid: string, name: string, batchId: string) {
995
1017
  this.expanded = false;
996
1018
  this.name = name;
997
1019
  this.tid = tid;
998
1020
  this.batchId = batchId;
1021
+ this.nothingtosync = false;
999
1022
  this.targets = new Array<TenantNode>();
1000
1023
  this.update(0, 0, 0, 0);
1001
1024
  }
@@ -1026,48 +1049,14 @@ export class APIResult {
1026
1049
  //
1027
1050
  // Azure AD Graph API
1028
1051
  //
1029
- //groupGet - GET /groups/{id}
1030
- export async function groupGet(tenant: Tenant, groupid: string): Promise<{ group: string, error: string }> {
1031
- // need a read or write access token to get graph users
1032
- let accessToken: string = "";
1033
- if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
1034
- accessToken = tenant.readServicePrincipal;
1035
- if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
1036
- accessToken = tenant.writeServicePrincipal;
1037
- if (accessToken === "") return { group: "", error: "no access token specified" };
1038
- // prepare Authorization headers as part of options
1039
- const headers = new Headers();
1040
- const bearer = `Bearer ${accessToken}`;
1041
- headers.append("Authorization", bearer);
1042
- let options = { method: "GET", headers: headers };
1043
- // make /groups endpoint call
1044
- try {
1045
- let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/${groupid}`;
1046
- let response = await fetch(groupsEndpoint, options);
1047
- let data = await response.json();
1048
- if (typeof data.error !== "undefined") {
1049
- return { group: "", error: `${data.error.code}: ${data.error.message}` };
1050
- }
1051
- return { group: data.value, error: `` };
1052
- }
1053
- catch (error: any) {
1054
- console.log(error);
1055
- return { group: "", error: `Exception: ${error}` };
1056
- }
1057
- }
1058
1052
  //groupsGet - GET /groups
1059
- export async function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
1060
- // need a read or write access token to get graph users
1061
- let accessToken: string = "";
1062
- if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
1063
- accessToken = tenant.readServicePrincipal;
1064
- if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
1065
- accessToken = tenant.writeServicePrincipal;
1066
- if (accessToken === "") return { groups: [], error: "no access token specified" };
1067
- // prepare Authorization headers as part of options
1068
- const headers = new Headers();
1069
- const bearer = `Bearer ${accessToken}`;
1070
- headers.append("Authorization", bearer);
1053
+ export async function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
1054
+ // need a logged in user to get graph users
1055
+ if (user == null || user.spacode == "") {
1056
+ return { groups: [], error: `500: invalid user passed to groupsGet` };
1057
+ }
1058
+ // create headers
1059
+ const headers = await defineHeaders(instance, user);
1071
1060
  let options = { method: "GET", headers: headers };
1072
1061
  // make /groups endpoint call
1073
1062
  try {
@@ -1081,7 +1070,7 @@ export async function groupsGet(tenant: Tenant, groupSearchString: string): Prom
1081
1070
  }
1082
1071
  catch (error: any) {
1083
1072
  console.log(error);
1084
- return { group: "", error: `Exception: ${error}` };
1073
+ return { groups: [], error: `Exception: ${error}` };
1085
1074
  }
1086
1075
  }
1087
1076
  export function signIn(user: User, tasks: TaskArray): void {
@@ -1295,18 +1284,13 @@ export async function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean
1295
1284
  return false; // failed, no need for UX to re-render
1296
1285
  }
1297
1286
  //usersGet - GET from AAD Users endpoint
1298
- export async function usersGet(tenant: Tenant): Promise<{ users: string[], error: string }> {
1299
- // need a read or write access token to get graph users
1300
- let accessToken: string = "";
1301
- if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
1302
- accessToken = tenant.readServicePrincipal;
1303
- if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
1304
- accessToken = tenant.writeServicePrincipal;
1305
- if (accessToken === "") return { users: [], error: "no access token specified" };
1306
- // prepare Authorization headers as part of options
1307
- const headers = new Headers();
1308
- const bearer = `Bearer ${accessToken}`;
1309
- headers.append("Authorization", bearer);
1287
+ export async function usersGet(instance: IPublicClientApplication, user: User | undefined): Promise<{ users: string[], error: string }> {
1288
+ // need a logged in user to get graph users
1289
+ if (user == null || user.spacode == "") {
1290
+ return { users: [], error: `500: invalid user passed to groupsGet` };
1291
+ }
1292
+ // create headers
1293
+ const headers = await defineHeaders(instance, user);
1310
1294
  let options = { method: "GET", headers: headers };
1311
1295
  // make /users endpoint call
1312
1296
  try {
@@ -1315,7 +1299,7 @@ export async function usersGet(tenant: Tenant): Promise<{ users: string[], error
1315
1299
  if (typeof data.error !== "undefined") {
1316
1300
  return { users: [], error: `${data.error.code}: ${data.error.message}` };
1317
1301
  }
1318
- let users = new Array<User>();
1302
+ let users = new Array<string>();
1319
1303
  for (let user of data.value) {
1320
1304
  users.push(user.mail);
1321
1305
  }
@@ -1496,8 +1480,6 @@ function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTena
1496
1480
  const regexMatch = item.authority.match(regex);
1497
1481
  tenant.authority = regexMatch ? regexMatch[1] : item.authority;
1498
1482
 
1499
- tenant.readServicePrincipal = item.readServicePrincipal;
1500
- tenant.writeServicePrincipal = item.writeServicePrincipal;
1501
1483
  // ensure this workspace tracks this tenant
1502
1484
  let idx = workspace.associatedTenants.findIndex((t) => t === item.tenantId);
1503
1485
  if (idx == -1) workspace.associatedTenants.push(item.tenantId);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mindline/sync",
3
3
  "type": "module",
4
- "version": "1.0.39",
4
+ "version": "1.0.41",
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/tenants.json CHANGED
@@ -6,8 +6,6 @@
6
6
  "tenantType": "",
7
7
  "permissionType": "",
8
8
  "onboarded": false,
9
- "authority": "",
10
- "readServicePrincipal": "",
11
- "writeServicePrincipal": ""
9
+ "authority": ""
12
10
  }
13
11
  ]
package/tenants2.json CHANGED
@@ -4,53 +4,41 @@
4
4
  "name": "Mindline1",
5
5
  "domain": "mindline1.onmicrosoft.com",
6
6
  "tenantType": "aad",
7
- "authority": "https://login.microsoftonline.com/common/",
8
- "readServicePrincipal": "TODO",
9
- "writeServicePrincipal": "TODO"
7
+ "authority": "https://login.microsoftonline.com/common/"
10
8
  },
11
9
  {
12
10
  "tid": "df9c2e0a-f6fe-43bb-a155-d51f66dffe0e",
13
11
  "name": "Mindline2",
14
12
  "domain": "mindline2.onmicrosoft.com",
15
13
  "tenantType": "aad",
16
- "authority": "https://login.microsoftonline.com/common/",
17
- "readServicePrincipal": "TODO",
18
- "writeServicePrincipal": "TODO"
14
+ "authority": "https://login.microsoftonline.com/common/"
19
15
  },
20
16
  {
21
17
  "tid": "1",
22
18
  "name": "WhoIam",
23
19
  "domain": "whoiam.onmicrosoft.com",
24
20
  "tenantType": "aad",
25
- "authority": "https://login.microsoftonline.com/common/",
26
- "readServicePrincipal": "TODO",
27
- "writeServicePrincipal": "TODO"
21
+ "authority": "https://login.microsoftonline.com/common/"
28
22
  },
29
23
  {
30
24
  "tid": "2",
31
25
  "name": "Grit Software",
32
26
  "domain": "gritsoftware.onmicrosoft.com",
33
27
  "tenantType": "aad",
34
- "authority": "https://login.microsoftonline.com/common/",
35
- "readServicePrincipal": "TODO",
36
- "writeServicePrincipal": "TODO"
28
+ "authority": "https://login.microsoftonline.com/common/"
37
29
  },
38
30
  {
39
31
  "tid": "3",
40
32
  "name": "Google",
41
33
  "domain": "google.onmicrosoft.com",
42
34
  "tenantType": "aad",
43
- "authority": "https://login.microsoftonline.com/common/",
44
- "readServicePrincipal": "TODO",
45
- "writeServicePrincipal": "TODO"
35
+ "authority": "https://login.microsoftonline.com/common/"
46
36
  },
47
37
  {
48
38
  "tid": "4",
49
39
  "name": "Trackman Golf",
50
40
  "domain": "trackman.onmicrosoft.com",
51
41
  "tenantType": "aad",
52
- "authority": "https://login.microsoftonline.com/common/",
53
- "readServicePrincipal": "TODO",
54
- "writeServicePrincipal": "TODO"
42
+ "authority": "https://login.microsoftonline.com/common/"
55
43
  }
56
44
  ]