@mindline/sync 1.0.36 → 1.0.37
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/.vs/VSWorkspaceState.json +0 -1
- package/.vs/slnx.sqlite +0 -0
- package/.vs/sync/FileContentIndex/08e0b916-d40e-4be9-82e8-27d6a5dba7e0.vsidx +0 -0
- package/.vs/sync/FileContentIndex/4199b40e-a51a-4aab-a20d-788df6fd8a20.vsidx +0 -0
- package/.vs/sync/v17/.wsuo +0 -0
- package/hybridspa.ts +12 -17
- package/index.d.ts +4 -2
- package/index.ts +282 -160
- package/package.json +4 -1
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +9 -0
- package/.vs/sync/FileContentIndex/65b0b1c9-0b62-4457-8808-4e1317638277.vsidx +0 -0
package/.vs/slnx.sqlite
CHANGED
|
Binary file
|
package/.vs/sync/v17/.wsuo
CHANGED
|
Binary file
|
package/hybridspa.ts
CHANGED
|
@@ -36,11 +36,17 @@ export const graphConfig = {
|
|
|
36
36
|
graphGroupsEndpoint: "https://graph.microsoft.com/v1.0/groups",
|
|
37
37
|
graphMailEndpoint: "https://graph.microsoft.com/v1.0/me/messages",
|
|
38
38
|
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me",
|
|
39
|
-
graphTenantByDomainEndpoint:
|
|
40
|
-
"https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByDomainName",
|
|
41
|
-
graphTenantByIdEndpoint:
|
|
42
|
-
"https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByTenantId",
|
|
43
39
|
graphUsersEndpoint: "https://graph.microsoft.com/v1.0/users",
|
|
40
|
+
// sovereign cloud tenant info endpoints
|
|
41
|
+
graphTenantByDomainPredicate: "beta/tenantRelationships/findTenantInformationByDomainName",
|
|
42
|
+
graphTenantByIdPredicate: "beta/tenantRelationships/findTenantInformationByTenantId",
|
|
43
|
+
// authority values are based on the well-known OIDC auth endpoints
|
|
44
|
+
authorityWW: "https://login.microsoftonline.com/",
|
|
45
|
+
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$/,
|
|
46
|
+
authorityUS: "https://login.microsoftonline.us/",
|
|
47
|
+
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$/,
|
|
48
|
+
authorityCN: "https://login.partner.microsoftonline.cn/",
|
|
49
|
+
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$/,
|
|
44
50
|
// reader endpoint to trigger sync start
|
|
45
51
|
readerStartSyncEndpoint: "https://dev-fn-reader-westus.azurewebsites.net/api/startSync/",
|
|
46
52
|
readerApiEndpoint: "https://dev-fn-reader-westus.azurewebsites.net/api/lookup/"
|
|
@@ -215,20 +221,9 @@ export async function adminPost(
|
|
|
215
221
|
workspaceId: string
|
|
216
222
|
): Promise<APIResult> {
|
|
217
223
|
let result: APIResult = new APIResult();
|
|
218
|
-
if (
|
|
219
|
-
user.mail == null ||
|
|
220
|
-
user.mail === "" ||
|
|
221
|
-
user.authority == null ||
|
|
222
|
-
user.authority === "" ||
|
|
223
|
-
user.tid == null ||
|
|
224
|
-
user.tid === "" ||
|
|
225
|
-
user.companyName == null ||
|
|
226
|
-
user.companyName === "" ||
|
|
227
|
-
user.companyDomain == null ||
|
|
228
|
-
user.companyDomain === ""
|
|
229
|
-
) {
|
|
224
|
+
if (user.mail == "" || user.authority == "" || user.tid === "") {
|
|
230
225
|
result.result = false;
|
|
231
|
-
result.error = "
|
|
226
|
+
result.error = "adminPost: invalid argument";
|
|
232
227
|
result.status = 500;
|
|
233
228
|
return result;
|
|
234
229
|
}
|
package/index.d.ts
CHANGED
|
@@ -139,13 +139,14 @@ declare module "@mindline/sync" {
|
|
|
139
139
|
constructor(config: Config|null, syncPortalGlobalState: InitInfo|null, bClearLocalStorage: boolean);
|
|
140
140
|
// populate tenantNodes based on config tenants
|
|
141
141
|
init(config: Config|null, syncPortalGlobalState: InitInfo|null, bClearLocalStorage: boolean): void;
|
|
142
|
-
startSync(instance: IPublicClientApplication, authorizedUser: User|null|undefined, config: Config|null|undefined):
|
|
142
|
+
startSync(instance: IPublicClientApplication, authorizedUser: User | null | undefined, config: Config | null | undefined, setConfigSyncResult: (syncUpdate: string) => void): APIResult;
|
|
143
143
|
}
|
|
144
144
|
export class TenantNode {
|
|
145
145
|
expanded: boolean;
|
|
146
146
|
status: string;
|
|
147
147
|
name: string;
|
|
148
148
|
tid: string;
|
|
149
|
+
batchId: string;
|
|
149
150
|
total: number;
|
|
150
151
|
read: number;
|
|
151
152
|
written: number;
|
|
@@ -170,7 +171,8 @@ declare module "@mindline/sync" {
|
|
|
170
171
|
export function signOut(user: User): void;
|
|
171
172
|
export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
|
|
172
173
|
export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
|
|
173
|
-
export function
|
|
174
|
+
export function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): Promise<boolean>;
|
|
175
|
+
export function usersGet(tenant: Tenant): { users: string[], error: string };
|
|
174
176
|
//
|
|
175
177
|
// Mindline Config API
|
|
176
178
|
//
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import tenants from "./tenants.json";
|
|
|
9
9
|
import configs from "./configs.json";
|
|
10
10
|
import workspaces from "./workspaces.json";
|
|
11
11
|
import tasksData from "./tasks";
|
|
12
|
+
import { log } from "console";
|
|
12
13
|
const FILTER_FIELD = "workspaceIDs";
|
|
13
14
|
// called by unit tests
|
|
14
15
|
export function sum(a: number, b: number): number {
|
|
@@ -42,7 +43,7 @@ export class User {
|
|
|
42
43
|
this.oid = "";
|
|
43
44
|
this.name = "";
|
|
44
45
|
this.mail = "";
|
|
45
|
-
this.authority = "
|
|
46
|
+
this.authority = "";
|
|
46
47
|
this.tid = "";
|
|
47
48
|
this.companyName = "";
|
|
48
49
|
this.companyDomain = "";
|
|
@@ -87,12 +88,20 @@ export class Tenant {
|
|
|
87
88
|
this.tenantType = "aad";
|
|
88
89
|
this.permissionType = "notassigned";
|
|
89
90
|
this.onboarded = "false";
|
|
90
|
-
this.authority = "
|
|
91
|
+
this.authority = "";
|
|
91
92
|
this.readServicePrincipal = "";
|
|
92
93
|
this.writeServicePrincipal = "";
|
|
93
94
|
this.workspaceIDs = "";
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
function getGraphEndpoint(authority: string): string {
|
|
98
|
+
switch (authority) {
|
|
99
|
+
case graphConfig.authorityWW: return "https://graph.microsoft.com/";
|
|
100
|
+
case graphConfig.authorityUS: return "https://graph.microsoft.us/";
|
|
101
|
+
case graphConfig.authorityCN: return "https://microsoftgraph.chinacloudapi.cn/";
|
|
102
|
+
default: debugger; return "";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
96
105
|
export enum TenantConfigType {
|
|
97
106
|
source = 1,
|
|
98
107
|
target = 2,
|
|
@@ -533,141 +542,179 @@ export class BatchArray {
|
|
|
533
542
|
}
|
|
534
543
|
}
|
|
535
544
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
let
|
|
551
|
-
|
|
552
|
-
let
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
545
|
+
// start a sync cycle
|
|
546
|
+
async startSync(instance: IPublicClientApplication, authorizedUser: User | null | undefined, config: Config | null | undefined, setConfigSyncResult: (syncUpdate: string) => void): Promise<APIResult> {
|
|
547
|
+
let result: APIResult = new APIResult();
|
|
548
|
+
if (this.tenantNodes == null || this.tenantNodes.length == 0) {
|
|
549
|
+
// we should not have an empty batch array for a test
|
|
550
|
+
debugger;
|
|
551
|
+
result.result = false;
|
|
552
|
+
result.error = "startSync: invalid parameters";
|
|
553
|
+
result.status = 500;
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
// define newMessage handler that can access *this*
|
|
557
|
+
let handler = (message) => {
|
|
558
|
+
console.log(message);
|
|
559
|
+
let item = JSON.parse(message);
|
|
560
|
+
// find the associated tenant for this SignalR message
|
|
561
|
+
let tenantNode: TenantNode = this.tenantNodes.find((t: TenantNode) => t.tid === item.TargetID);
|
|
562
|
+
if (tenantNode == null) { // null OR undefined
|
|
563
|
+
console.log(`${item.TargetID} not found in BatchArray.`);
|
|
564
|
+
debugger;
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
let writerNode: TenantNode|null = null;
|
|
568
|
+
// process stats for this SignalR message
|
|
569
|
+
let statsarray = item.Stats; // get the array of statistics
|
|
570
|
+
let statskeys = Object.keys(statsarray); // get the keys of the array
|
|
571
|
+
let statsvalues = Object.values(statsarray); // get the values of the array
|
|
572
|
+
for (let j = 0; j < statskeys.length; j++) {
|
|
573
|
+
if (statskeys[j].startsWith("Reader")) {
|
|
574
|
+
if (statskeys[j].endsWith("TotalCount")) {
|
|
575
|
+
// parse batchId from key and store in TenantNode for this batch
|
|
576
|
+
let batchidRegexp = /Reader\/BID:(.+)\/TotalCount/;
|
|
577
|
+
let matchBID = statskeys[j].match(batchidRegexp);
|
|
578
|
+
if (matchBID == null) {
|
|
579
|
+
console.log(`batchId not found in ${statskeys[j]}.`);
|
|
580
|
+
debugger;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
// integrate any queued Writers that have been waiting for this batchId to be assigned to a Reader
|
|
584
|
+
let idx: number = this.tenantNodes.findIndex((t: TenantNode) => (t.name === "QUEUED WRITER" && t.batchId === matchBID[1]));
|
|
585
|
+
while (idx !== -1) {
|
|
586
|
+
let queuedWriter: TenantNode = this.tenantNodes.splice(idx, 1)[0];
|
|
587
|
+
let actualWriter: TenantNode = tenantNode.targets.find((t: TenantNode) => t.tid == queuedWriter.tid);
|
|
588
|
+
if (actualWriter == null) {
|
|
589
|
+
console.log(`could not find queued writer ${queuedWriter.tid}.`);
|
|
590
|
+
debugger;
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
actualWriter.update(queuedWriter.total, queuedWriter.read, queuedWriter.written, queuedWriter.deferred);
|
|
594
|
+
}
|
|
595
|
+
idx = this.tenantNodes.findIndex((t: TenantNode) => (t.name === "QUEUED WRITER" && t.batchId === matchBID[1]));
|
|
596
|
+
}
|
|
597
|
+
// update Read node
|
|
598
|
+
tenantNode.batchId = matchBID[1];
|
|
599
|
+
tenantNode.total = Number(statsvalues[j]);
|
|
600
|
+
console.log(`----- ${tenantNode.name} batchId: ${tenantNode.batchId}`);
|
|
601
|
+
console.log(`----- ${tenantNode.name} Total Read: ${tenantNode.total}`);
|
|
562
602
|
}
|
|
563
|
-
if (statskeys[j].
|
|
564
|
-
|
|
603
|
+
if (statskeys[j].endsWith("CurrentCount")) {
|
|
604
|
+
tenantNode.read = Number(statsvalues[j]);
|
|
605
|
+
console.log(`----- ${tenantNode.name} Currently Read: ${tenantNode.read}`);
|
|
565
606
|
}
|
|
566
607
|
}
|
|
567
|
-
if (statskeys[j].
|
|
568
|
-
|
|
608
|
+
if (statskeys[j].startsWith("Writer")) {
|
|
609
|
+
if (statskeys[j].endsWith("TotalCount")) {
|
|
610
|
+
// parse batchId from Writer key
|
|
611
|
+
let batchidRegexp = /Writer\/BID:(.+)\/TotalCount/;
|
|
612
|
+
let matchBID = statskeys[j].match(batchidRegexp);
|
|
613
|
+
if (matchBID == null) {
|
|
614
|
+
console.log(`batchId not found in ${statskeys[j]}.`);
|
|
615
|
+
debugger;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// find the Writer node for this tenant under the Reader node of a different tenant
|
|
619
|
+
let readerNode: TenantNode = this.tenantNodes.find((t: TenantNode) => t.batchId === matchBID[1]);
|
|
620
|
+
if (readerNode == null) {
|
|
621
|
+
// reader for this batch has not yet been encountered, queue this writer for processing when reader arrives
|
|
622
|
+
writerNode = new TenantNode(tenantNode.tid, "QUEUED WRITER");
|
|
623
|
+
writerNode.batchId = matchBID[1];
|
|
624
|
+
writerNode.total = Number(statsvalues[j]);
|
|
625
|
+
this.tenantNodes.push(writerNode);
|
|
626
|
+
console.log(`----- QUEUED batchId: ${writerNode.batchId}`);
|
|
627
|
+
console.log(`----- QUEUED Total To Write: ${writerNode.total}`);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
if (readerNode.name == "QUEUED WRITER") {
|
|
631
|
+
// update queued node
|
|
632
|
+
writerNode = readerNode;
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
// reader is legit, find writer node under this reader node
|
|
636
|
+
writerNode = readerNode.targets.find((t: TenantNode) => t.tid === tenantNode.tid);
|
|
637
|
+
if (writerNode == null) {
|
|
638
|
+
console.log(`Writer ${tenantNode.name} not found under Reader ${readerNode.name}.`);
|
|
639
|
+
debugger;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
writerNode.batchId = matchBID[1];
|
|
644
|
+
writerNode.total = Number(statsvalues[j]);
|
|
645
|
+
console.log(`----- ${writerNode.name} batchId: ${writerNode.batchId}`);
|
|
646
|
+
console.log(`----- ${writerNode.name} Total To Write: ${writerNode.total}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (statskeys[j].endsWith("CurrentCount")) {
|
|
650
|
+
// parse batchId from Writer key
|
|
651
|
+
let batchidRegexp = /Writer\/BID:(.+)\/CurrentCount/;
|
|
652
|
+
let matchBID = statskeys[j].match(batchidRegexp);
|
|
653
|
+
if (matchBID == null) {
|
|
654
|
+
console.log(`batchId not found in ${statskeys[j]}.`);
|
|
655
|
+
debugger;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// find the Writer node for this tenant under the Reader node of a different tenant
|
|
659
|
+
let readerNode: TenantNode = this.tenantNodes.find((t: TenantNode) => t.batchId === matchBID[1]);
|
|
660
|
+
if (readerNode == null) {
|
|
661
|
+
// no reader or queued writer for this batch
|
|
662
|
+
console.log(`No reader or queued writer for batch ${matchBID[1]}.`);
|
|
663
|
+
debugger;
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (readerNode.name == "QUEUED WRITER") {
|
|
667
|
+
// update queued node
|
|
668
|
+
writerNode = readerNode;
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
// reader is legit, find writer node under this reader node
|
|
672
|
+
writerNode = readerNode.targets.find((t: TenantNode) => t.tid === tenantNode.tid);
|
|
673
|
+
if (writerNode == null) {
|
|
674
|
+
console.log(`Writer ${tenantNode.name} not found under Reader ${readerNode.name}.`);
|
|
675
|
+
debugger;
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
writerNode.batchId = matchBID[1];
|
|
680
|
+
writerNode.written = Number(statsvalues[j]);
|
|
681
|
+
console.log(`${writerNode.name} batchId: ${writerNode.batchId}`);
|
|
682
|
+
console.log(`${writerNode.name} Total Written: ${writerNode.written}`);
|
|
683
|
+
}
|
|
569
684
|
}
|
|
570
|
-
statistics = statistics + statskeys[j] + "=" + statsvalues[j] + "<br />" //
|
|
571
685
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
function myFunction(targetid, str) {
|
|
579
|
-
const table = document.getElementById("the-table");
|
|
580
|
-
let row = document.getElementById(targetid);
|
|
581
|
-
if (row) {
|
|
582
|
-
row.cells[1].innerHTML = "<code>" + str + "</ code>";
|
|
583
|
-
} else {
|
|
584
|
-
row = table.insertRow(1);
|
|
585
|
-
row.id = targetid;
|
|
586
|
-
let cell1 = row.insertCell(0);
|
|
587
|
-
let cell2 = row.insertCell(1);
|
|
588
|
-
let caption = (targetid === "00000000-0000-0000-0000-000000000000") ? "<B>Status of ServiceBus QUEUES</B>" : targetid;
|
|
589
|
-
cell1.innerHTML = "<code>" + caption + "</ code>";
|
|
590
|
-
cell2.innerHTML = "<code>" + str + "</ code>";
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function updateProgress(total, currentR, currentW, currentD) {
|
|
595
|
-
updateRead(total, currentR);
|
|
596
|
-
updateWrite(total, currentW, currentD);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function updateRead(total, current) {
|
|
600
|
-
const element = document.getElementById("readBar");
|
|
601
|
-
const width = Math.round(100 * current / total);
|
|
602
|
-
element.style.width = width + '%';
|
|
603
|
-
element.innerHTML = "R=" + width + '%';
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function updateWrite(total, w, d) {
|
|
607
|
-
const elementW = document.getElementById("writeBar");
|
|
608
|
-
const widthW = Math.round(100 * w / total);
|
|
609
|
-
elementW.style.width = widthW + '%';
|
|
610
|
-
elementW.innerHTML = "W=" + widthW + '%';
|
|
611
|
-
|
|
612
|
-
const elementD = document.getElementById("deferBar");
|
|
613
|
-
let width = 0;
|
|
614
|
-
let widthScaled = 0;
|
|
615
|
-
if (d > 0) {
|
|
616
|
-
width = Math.round(100 * d / total) + 1;
|
|
617
|
-
if (width < 10) {
|
|
618
|
-
widthScaled = width * 10;
|
|
619
|
-
}
|
|
620
|
-
else if (width < 20) {
|
|
621
|
-
widthScaled = width * 5;
|
|
686
|
+
tenantNode.update(tenantNode.total, tenantNode.read, tenantNode.written, tenantNode.deferred);
|
|
687
|
+
if (writerNode != null) {
|
|
688
|
+
writerNode.update(writerNode.total, writerNode.read, writerNode.written, writerNode.deferred);
|
|
689
|
+
if (tenantNode.total === tenantNode.read && writerNode.total === writerNode.written) {
|
|
690
|
+
setConfigSyncResult("complete");
|
|
691
|
+
console.log(`Setting config sync result: "complete"`);
|
|
622
692
|
}
|
|
623
693
|
else {
|
|
624
|
-
|
|
694
|
+
setConfigSyncResult("writing in progress");
|
|
695
|
+
console.log(`Setting config sync result: "writing in progress"`);
|
|
625
696
|
}
|
|
626
697
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
elementD.style.width = widthScaled + '%';
|
|
631
|
-
elementD.innerHTML = width + '%';
|
|
632
|
-
document.getElementById("deferred").innerHTML = 'Deferred:' + width + '% (' + d + ' of ' + total + ')';
|
|
633
|
-
// cycle through desired states for sources and targets
|
|
634
|
-
if (this.tenantNodes != null) {
|
|
635
|
-
this.tenantNodes.map((sourceTenantNode: TenantNode) => {
|
|
636
|
-
if (sourceTenantNode.read == 0) sourceTenantNode.update(100, 50, 0, 0);
|
|
637
|
-
else if (sourceTenantNode.read == 50) sourceTenantNode.update(100, 100, 0, 0);
|
|
638
|
-
else sourceTenantNode.update(0, 0, 0, 0);
|
|
639
|
-
if (sourceTenantNode.targets != null) {
|
|
640
|
-
sourceTenantNode.targets.map((targetTenantNode: TenantNode) => {
|
|
641
|
-
if (targetTenantNode.written == 0) targetTenantNode.update(100, 0, 50, 0);
|
|
642
|
-
else if (targetTenantNode.written == 50) targetTenantNode.update(100, 0, 100, 0);
|
|
643
|
-
else if (targetTenantNode.written == 100) targetTenantNode.update(100, 0, 99, 1);
|
|
644
|
-
else targetTenantNode.update(0, 0, 0, 0);
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
});
|
|
698
|
+
else {
|
|
699
|
+
setConfigSyncResult("reading in progress");
|
|
700
|
+
console.log(`Setting config sync result: "reading in progress"`);
|
|
648
701
|
}
|
|
649
702
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
.
|
|
661
|
-
.configureLogging(signalR.LogLevel.Information)
|
|
662
|
-
.build();
|
|
663
|
-
// when you get a message, log the message
|
|
664
|
-
connection.on("newMessage", function (message) {
|
|
665
|
-
console.log(message); // log the message
|
|
703
|
+
// start SignalR connection for each source tenant in the configuration
|
|
704
|
+
this.tenantNodes.map((sourceTenantNode: TenantNode) => {
|
|
705
|
+
const endpointUrl = `https://dev-signalrdispatcher-westus.azurewebsites.net/statsHub?statsId=${sourceTenantNode.tid}`;
|
|
706
|
+
const connection: signalR.HubConnection = new signalR.HubConnectionBuilder()
|
|
707
|
+
.withUrl(endpointUrl)
|
|
708
|
+
// .withAutomaticReconnect() TODO: full auto-reconnect implementation
|
|
709
|
+
.configureLogging(signalR.LogLevel.Information)
|
|
710
|
+
.build();
|
|
711
|
+
// when you get a message, process the message
|
|
712
|
+
connection.on("newMessage", handler);
|
|
713
|
+
connection.start().catch(console.error);
|
|
666
714
|
});
|
|
667
|
-
connection.start().catch(console.error);
|
|
668
715
|
// execute post to reader endpoint
|
|
669
|
-
readerPost(instance, authorizedUser, config);
|
|
670
|
-
|
|
716
|
+
result = await readerPost(instance, authorizedUser, config);
|
|
717
|
+
return result;
|
|
671
718
|
}
|
|
672
719
|
}
|
|
673
720
|
export class TenantNode {
|
|
@@ -675,6 +722,7 @@ export class TenantNode {
|
|
|
675
722
|
status: string;
|
|
676
723
|
name: string;
|
|
677
724
|
tid: string;
|
|
725
|
+
batchId: string;
|
|
678
726
|
total: number;
|
|
679
727
|
read: number;
|
|
680
728
|
written: number;
|
|
@@ -684,6 +732,7 @@ export class TenantNode {
|
|
|
684
732
|
this.expanded = false;
|
|
685
733
|
this.name = name;
|
|
686
734
|
this.tid = tid;
|
|
735
|
+
this.batchId = "";
|
|
687
736
|
this.targets = new Array<TenantNode>();
|
|
688
737
|
this.update(0, 0, 0, 0);
|
|
689
738
|
}
|
|
@@ -815,9 +864,9 @@ export function signOut(user: User): void {
|
|
|
815
864
|
export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): Promise<boolean> {
|
|
816
865
|
if (debug) debugger;
|
|
817
866
|
// do we already have a valid tenant name? if so, nothing to add
|
|
818
|
-
if (
|
|
867
|
+
if (tenant.name != null && tenant.name !== "") return false;
|
|
819
868
|
// if needed, retrieve and cache access token
|
|
820
|
-
if (
|
|
869
|
+
if (loggedInUser.accessToken != null && loggedInUser.accessToken === "") {
|
|
821
870
|
console.log(`tenantRelationshipsGetByDomain called with invalid logged in user: ${loggedInUser.name}`);
|
|
822
871
|
try {
|
|
823
872
|
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
|
|
@@ -837,29 +886,32 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
|
|
|
837
886
|
// make tenant endpoint call
|
|
838
887
|
try {
|
|
839
888
|
// create tenant info endpoint
|
|
840
|
-
var tenantEndpoint = graphConfig.
|
|
889
|
+
var tenantEndpoint = getGraphEndpoint(tenant.authority) + graphConfig.graphTenantByDomainPredicate;
|
|
841
890
|
tenantEndpoint += "(domainName='";
|
|
842
891
|
tenantEndpoint += tenant.domain;
|
|
843
892
|
tenantEndpoint += "')";
|
|
844
893
|
console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
|
|
845
894
|
let response = await fetch(tenantEndpoint, options);
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if (
|
|
849
|
-
|
|
850
|
-
|
|
895
|
+
if (response.status == 200 && response.statusText == "OK") {
|
|
896
|
+
let data = await response.json();
|
|
897
|
+
if (data) {
|
|
898
|
+
if (data.error != null) {
|
|
899
|
+
debugger;
|
|
900
|
+
console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
else if (data.displayName != null && data.displayName !== "") {
|
|
904
|
+
// set domain information on passed tenant
|
|
905
|
+
tenant.tid = data.tenantId;
|
|
906
|
+
tenant.name = data.displayName;
|
|
907
|
+
console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
|
|
908
|
+
return true; // success, need UX to re-render
|
|
909
|
+
}
|
|
851
910
|
}
|
|
852
|
-
else
|
|
853
|
-
|
|
854
|
-
tenant.tid = data.tenantId;
|
|
855
|
-
tenant.name = data.displayName;
|
|
856
|
-
console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
|
|
857
|
-
return true; // success, need UX to re-render
|
|
911
|
+
else {
|
|
912
|
+
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
858
913
|
}
|
|
859
914
|
}
|
|
860
|
-
else {
|
|
861
|
-
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
862
|
-
}
|
|
863
915
|
}
|
|
864
916
|
catch (error: any) {
|
|
865
917
|
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
@@ -871,9 +923,9 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
|
|
|
871
923
|
export async function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): Promise<boolean> {
|
|
872
924
|
if (debug) debugger;
|
|
873
925
|
// do we already have a valid company name? if so, nothing to add, no need for UX to re-render
|
|
874
|
-
if (
|
|
926
|
+
if (user.companyName != "") return false;
|
|
875
927
|
// if needed, retrieve and cache access token
|
|
876
|
-
if (
|
|
928
|
+
if (user.accessToken === "") {
|
|
877
929
|
try {
|
|
878
930
|
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
|
|
879
931
|
user.accessToken = response.accessToken; // cache access token
|
|
@@ -892,7 +944,7 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
892
944
|
// make tenant endpoint call
|
|
893
945
|
try {
|
|
894
946
|
// create tenant info endpoint
|
|
895
|
-
var tenantEndpoint = graphConfig.
|
|
947
|
+
var tenantEndpoint = getGraphEndpoint(user.authority) + graphConfig.graphTenantByIdPredicate;
|
|
896
948
|
tenantEndpoint += "(tenantId='";
|
|
897
949
|
tenantEndpoint += user.tid;
|
|
898
950
|
tenantEndpoint += "')";
|
|
@@ -931,6 +983,54 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
931
983
|
tasks.setTaskEnd("GET tenant details", new Date(), "failed");
|
|
932
984
|
return false; // failed, no need for UX to re-render
|
|
933
985
|
}
|
|
986
|
+
//tenantUnauthenticatedLookup (from https://gettenantpartitionweb.azurewebsites.net/js/gettenantpartition.js)
|
|
987
|
+
export async function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): Promise<boolean> {
|
|
988
|
+
if (debug) debugger;
|
|
989
|
+
// do we already have a valid tenant ID? if so, nothing to add
|
|
990
|
+
if (tenant.tid !== "") return false;
|
|
991
|
+
// do we not have a valid domain? if so, nothing to lookup
|
|
992
|
+
if (tenant.domain == "") return false;
|
|
993
|
+
// prepare the 3 endpoints and corresponding regular expressions
|
|
994
|
+
let endpoints: string[] = [graphConfig.authorityWW, graphConfig.authorityUS, graphConfig.authorityCN];
|
|
995
|
+
let regexes: RegExp[] = [graphConfig.authorityWWRegex, graphConfig.authorityUSRegex, graphConfig.authorityCNRegex];
|
|
996
|
+
// make unauthenticated well-known openid endpoint call(s)
|
|
997
|
+
let response = null;
|
|
998
|
+
try {
|
|
999
|
+
for (let j = 0; j < 3; j++) {
|
|
1000
|
+
// create well-known openid endpoint
|
|
1001
|
+
var openidEndpoint = endpoints[j];
|
|
1002
|
+
openidEndpoint += tenant.domain;
|
|
1003
|
+
openidEndpoint += "/.well-known/openid-configuration";
|
|
1004
|
+
console.log("Attempting GET from openid well-known endpoint: ", openidEndpoint);
|
|
1005
|
+
response = await fetch(openidEndpoint);
|
|
1006
|
+
if (response.status == 200 && response.statusText == "OK") {
|
|
1007
|
+
let data = await response.json();
|
|
1008
|
+
if (data) {
|
|
1009
|
+
// store tenant ID and authority
|
|
1010
|
+
var tenantAuthEndpoint = data.authorization_endpoint;
|
|
1011
|
+
var authMatches = tenantAuthEndpoint.match(regexes[j]);
|
|
1012
|
+
tenant.tid = authMatches[2];
|
|
1013
|
+
tenant.authority = authMatches[1]; // USGov tenants are registered in WW with USGov authority values!
|
|
1014
|
+
console.log("Successful GET from openid well-known endpoint");
|
|
1015
|
+
return true; // success, need UX to re-render
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
console.log(`Failed JSON parse of openid well-known endpoint response ${openidEndpoint}.`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
console.log(`Failed GET from ${openidEndpoint}.`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
catch (error: any) {
|
|
1027
|
+
console.log("Failed to GET from openid well-known endpoint: ", error);
|
|
1028
|
+
}
|
|
1029
|
+
if (tenant.tid == "" || tenant.authority == "") {
|
|
1030
|
+
console.log(`GET from openid well-known endpoint failed to find tenant: ${response ? response.statusText : "unknown"}`);
|
|
1031
|
+
}
|
|
1032
|
+
return false; // failed, no need for UX to re-render
|
|
1033
|
+
}
|
|
934
1034
|
//usersGet - GET from AAD Users endpoint
|
|
935
1035
|
export async function usersGet(tenant: Tenant): Promise<{ users: string[], error: string }> {
|
|
936
1036
|
// need a read or write access token to get graph users
|
|
@@ -1011,23 +1111,45 @@ export async function configsRefresh(instance: IPublicClientApplication, authori
|
|
|
1011
1111
|
export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult> {
|
|
1012
1112
|
let result: APIResult = new APIResult();
|
|
1013
1113
|
if (debug) debugger;
|
|
1014
|
-
//
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1114
|
+
// lookup authority for this user (the lookup call does it based on domain, but TID works as well to find authority)
|
|
1115
|
+
let tenant: Tenant = new Tenant();
|
|
1116
|
+
tenant.domain = user.tid;
|
|
1117
|
+
let bResult: boolean = await tenantUnauthenticatedLookup(tenant, debug);
|
|
1118
|
+
if (bResult) {
|
|
1119
|
+
// success, we at least got authority as a new bit of information at this point
|
|
1120
|
+
user.authority = tenant.authority;
|
|
1121
|
+
// do we have a logged in user from the same authority as this newly proposed tenant?
|
|
1122
|
+
let loggedInUser: User | undefined = ii.us.find((u: User) => (u.session === "Sign Out" && u.authority === user.authority));
|
|
1123
|
+
if (loggedInUser != null) {
|
|
1124
|
+
// get tenant name and domain from AAD
|
|
1125
|
+
result.result = await tenantRelationshipsGetById(user, ii, instance, tasks, debug);
|
|
1126
|
+
// if this is the first time, we have just gotten tenant info, then we must POST user and not-yet-onboarded tenant to back end
|
|
1127
|
+
if (result.result) {
|
|
1128
|
+
tasks.setTaskStart("POST config init", new Date());
|
|
1129
|
+
result = await initPost(instance, authorizedUser, user, debug);
|
|
1130
|
+
tasks.setTaskEnd("POST config init", new Date(), result.result ? "complete" : "failed");
|
|
1131
|
+
}
|
|
1132
|
+
// simlarly, if we just did our first post, then query config backend for workspace(s) associated with this user
|
|
1133
|
+
if (result.result) {
|
|
1134
|
+
tasks.setTaskStart("GET workspaces", new Date());
|
|
1135
|
+
result = await workspaceInfoGet(instance, authorizedUser, user, ii, debug);
|
|
1136
|
+
tasks.setTaskEnd("GET workspaces", new Date(), result.result ? "complete" : "failed");
|
|
1137
|
+
}
|
|
1138
|
+
if (result.result) result.error = version;
|
|
1139
|
+
else console.log("@mindline/sync package version: " + version);
|
|
1140
|
+
return result;
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
result.error = `${user.mail} insufficient privileges to lookup under authority: ${user.authority}.`;
|
|
1144
|
+
result.result = false;
|
|
1145
|
+
return result;
|
|
1146
|
+
}
|
|
1021
1147
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
result
|
|
1026
|
-
tasks.setTaskEnd("GET workspaces", new Date(), result.result ? "complete" : "failed");
|
|
1148
|
+
else {
|
|
1149
|
+
result.error = `Failed to retrieve authority for user "${user.mail}" TID ${user.tid}.`;
|
|
1150
|
+
result.result = false;
|
|
1151
|
+
return result;
|
|
1027
1152
|
}
|
|
1028
|
-
if (result.result) result.error = version;
|
|
1029
|
-
else console.log("@mindline/sync package version: " + version);
|
|
1030
|
-
return result;
|
|
1031
1153
|
}
|
|
1032
1154
|
export async function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): Promise<APIResult> {
|
|
1033
1155
|
return tenantPost(instance, authorizedUser, tenant, workspaceId);
|
|
@@ -1091,7 +1213,7 @@ function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTena
|
|
|
1091
1213
|
// clear and overwrite dummy
|
|
1092
1214
|
tenant = ii.ts.at(dummyIndex);
|
|
1093
1215
|
} else {
|
|
1094
|
-
// create and track new
|
|
1216
|
+
// create and track new tenant
|
|
1095
1217
|
tenant = new Tenant();
|
|
1096
1218
|
ii.ts.push(tenant);
|
|
1097
1219
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindline/sync",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.37",
|
|
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.",
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"author": "",
|
|
14
14
|
"license": "ISC",
|
|
15
15
|
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.2.21",
|
|
17
|
+
"@types/react-dom": "^18.2.7",
|
|
18
|
+
"typescript": "^5.2.2",
|
|
16
19
|
"vitest": "^0.29.8"
|
|
17
20
|
},
|
|
18
21
|
"dependencies": {
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"lib": [ "DOM", "DOM.Iterable", "ESNext" ],
|
|
5
|
+
"types": [ "vite/client", "vite-plugin-svgr/client", "node", "jest" ],
|
|
6
|
+
"allowJs": false,
|
|
7
|
+
"skipLibCheck": false,
|
|
8
|
+
"esModuleInterop": false,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"composite": false,
|
|
18
|
+
|
|
19
|
+
/* Linting */
|
|
20
|
+
"strict": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src"],
|
|
26
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
27
|
+
}
|
|
Binary file
|