@mindline/sync 1.0.35 → 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 +13 -20
- package/index.d.ts +176 -175
- package/index.ts +314 -153
- package/package.json +5 -1
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +9 -0
- package/.vs/sync/FileContentIndex/1fe38d21-0fca-4d45-9e42-8c354c1a37f7.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
|
}
|
|
@@ -863,14 +858,12 @@ export async function readerPost(
|
|
|
863
858
|
return result;
|
|
864
859
|
}
|
|
865
860
|
// create reader endpoint with config ID
|
|
866
|
-
|
|
867
|
-
let readerEndpoint: string = graphConfig.readerApiEndpoint + config.id;
|
|
861
|
+
let readerEndpoint: string = graphConfig.readerStartSyncEndpoint + config.id;
|
|
868
862
|
// create headers
|
|
869
863
|
const headers = await defineHeaders(instance, authorizedUser);
|
|
870
864
|
// make reader endpoint call
|
|
871
865
|
let options = { method: "POST", headers: headers };
|
|
872
866
|
try {
|
|
873
|
-
debugger;
|
|
874
867
|
console.log("Attempting POST to /startSync: " + readerEndpoint);
|
|
875
868
|
let response = await fetch(readerEndpoint, options);
|
|
876
869
|
if (response.status === 200 && response.statusText === "OK") {
|
package/index.d.ts
CHANGED
|
@@ -1,187 +1,188 @@
|
|
|
1
1
|
import { IPublicClientApplication } from "@azure/msal-browser";
|
|
2
2
|
|
|
3
3
|
declare module "@mindline/sync" {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export function sum(a: number, b: number): number;
|
|
5
|
+
export function helloNpm(): string;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
7
|
+
export class Group {
|
|
8
|
+
id: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
// admin
|
|
13
|
+
export class User {
|
|
14
|
+
oid: string; // from AAD ID token
|
|
15
|
+
name: string; // from AAD ID token
|
|
16
|
+
mail: string; // from AAD ID token TODO: preferred_username *may* differ from UPN, may differ from mail
|
|
17
|
+
authority: string; // from AAD auth response - cloud instance login endpoint
|
|
18
|
+
tid: string; // from AAD ID token
|
|
19
|
+
companyName: string; // findTenantInformationByTenantId TODO: process changes to company name
|
|
20
|
+
companyDomain: string; // findTenantInformationByTenantId TODO: process changes to company name
|
|
21
|
+
associatedWorkspaces: string[];
|
|
22
|
+
workspaceIDs: string;
|
|
23
|
+
session: string;
|
|
24
|
+
spacode: string;
|
|
25
|
+
accessToken: string;
|
|
26
|
+
loginHint: string;
|
|
27
|
+
scopes: string[];
|
|
28
|
+
authTS: Date;
|
|
29
|
+
constructor();
|
|
30
|
+
}
|
|
31
|
+
// tenant (Azure AD tenant, AD domain, Google workspace)
|
|
32
|
+
export enum TenantType {
|
|
33
|
+
invalid = 0,
|
|
34
|
+
aad = 1,
|
|
35
|
+
ad = 2,
|
|
36
|
+
googleworkspace = 3
|
|
37
|
+
}
|
|
38
|
+
type TenantTypeStrings = keyof typeof TenantType;
|
|
39
|
+
export enum TenantPermissionType {
|
|
40
|
+
read = 1,
|
|
41
|
+
write = 2,
|
|
42
|
+
notassigned = 3
|
|
43
|
+
}
|
|
44
|
+
type TenantPermissionTypeStrings = keyof typeof TenantPermissionType;
|
|
45
|
+
export class Tenant {
|
|
46
|
+
tid: string; // from AAD ID token
|
|
47
|
+
name: string; // findTenantInformationByTenantId
|
|
48
|
+
domain: string; // findTenantInformationByTenantId
|
|
49
|
+
tenantType: TenantTypeStrings; // always "aad" for now
|
|
50
|
+
permissionType: TenantPermissionTypeStrings; // read/write/notassigned
|
|
51
|
+
onboarded: string; // have we onboarded this tenant? "true" or "false"
|
|
52
|
+
authority: string; // from AAD ID auth response
|
|
53
|
+
readServicePrincipal: string; // from AAD consent
|
|
54
|
+
writeServicePrincipal: string; // from AAD consent
|
|
55
|
+
workspaceIDs: string;
|
|
56
|
+
constructor();
|
|
57
|
+
}
|
|
58
|
+
// config
|
|
59
|
+
export enum TenantConfigType {
|
|
60
|
+
source = 1,
|
|
61
|
+
target = 2,
|
|
62
|
+
sourcetarget = 3
|
|
63
|
+
}
|
|
64
|
+
export type TenantConfigTypeStrings = keyof typeof TenantConfigType;
|
|
65
|
+
export class TenantConfigInfo {
|
|
66
|
+
tid: string; // tenant identifier
|
|
67
|
+
sourceGroupId: string; // source group - we can configure source group for reading
|
|
68
|
+
sourceGroupName: string; // source group - we can configure source group for reading
|
|
69
|
+
configurationTenantType: TenantConfigTypeStrings;
|
|
70
|
+
deltaToken: string;
|
|
71
|
+
filesWritten: number;
|
|
72
|
+
configId: string;
|
|
73
|
+
}
|
|
74
|
+
export class Config {
|
|
75
|
+
id: string;
|
|
76
|
+
workspaceId: string;
|
|
77
|
+
name: string;
|
|
78
|
+
description: string;
|
|
79
|
+
tenants: TenantConfigInfo[];
|
|
80
|
+
isEnabled: boolean;
|
|
81
|
+
workspaceIDs: string;
|
|
82
|
+
constructor();
|
|
83
|
+
}
|
|
84
|
+
// class to group Users, Tenants, and Configs
|
|
85
|
+
export class Workspace {
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
associatedUsers: string[];
|
|
89
|
+
associatedTenants: string[];
|
|
90
|
+
associatedConfigs: string[];
|
|
91
|
+
constructor();
|
|
92
|
+
}
|
|
93
|
+
export class InitInfo {
|
|
94
|
+
us: User[];
|
|
95
|
+
ts: Tenant[];
|
|
96
|
+
cs: Config[];
|
|
97
|
+
ws: Workspace[];
|
|
98
|
+
constructor(bClearLocalStorage: boolean);
|
|
99
|
+
init(bClearLocalStorage: boolean): void;
|
|
100
|
+
save(): void;
|
|
101
|
+
tagWithWorkspaces(): boolean;
|
|
102
|
+
}
|
|
103
|
+
export type TaskType = "initialization" |
|
|
103
104
|
"authenticate user" |
|
|
104
105
|
"reload React" |
|
|
105
106
|
"PUT access token" |
|
|
106
107
|
"GET tenant details" |
|
|
107
108
|
"POST config init" |
|
|
108
109
|
"GET workspaces" |
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
110
|
+
"onboard tenant" |
|
|
111
|
+
"create 2nd tenant" |
|
|
112
|
+
"invite 2nd admin" |
|
|
113
|
+
"onboard 2nd tenant" |
|
|
114
|
+
"create config";
|
|
115
|
+
export class TaskArray {
|
|
116
|
+
tasks: Task[];
|
|
117
|
+
constructor(bClearLocalStorage: boolean);
|
|
118
|
+
init(bClearLocalStorage: boolean): void;
|
|
119
|
+
setTaskStart(taskType: TaskType, startDate: Date): void;
|
|
120
|
+
setTaskEnd(taskType: TaskType, startDate: Date, status: string): void;
|
|
121
|
+
}
|
|
122
|
+
export class Task {
|
|
123
|
+
id: number;
|
|
124
|
+
task: string;
|
|
125
|
+
start: Date;
|
|
126
|
+
startDisplay: string;
|
|
127
|
+
end: Date;
|
|
128
|
+
endDisplay: string;
|
|
129
|
+
elapsedDisplay: string;
|
|
130
|
+
expected: number;
|
|
131
|
+
status: string;
|
|
132
|
+
expanded: boolean;
|
|
133
|
+
subtasks: Task[];
|
|
134
|
+
setEnd(endDate: Date): void;
|
|
135
|
+
setStart(startDate: Date): void;
|
|
136
|
+
}
|
|
137
|
+
export class BatchArray {
|
|
138
|
+
tenantNodes: TenantNode[];
|
|
139
|
+
constructor(config: Config|null, syncPortalGlobalState: InitInfo|null, bClearLocalStorage: boolean);
|
|
140
|
+
// populate tenantNodes based on config tenants
|
|
141
|
+
init(config: Config|null, syncPortalGlobalState: InitInfo|null, bClearLocalStorage: boolean): void;
|
|
142
|
+
startSync(instance: IPublicClientApplication, authorizedUser: User | null | undefined, config: Config | null | undefined, setConfigSyncResult: (syncUpdate: string) => void): APIResult;
|
|
143
|
+
}
|
|
144
|
+
export class TenantNode {
|
|
145
|
+
expanded: boolean;
|
|
146
|
+
status: string;
|
|
147
|
+
name: string;
|
|
148
|
+
tid: string;
|
|
149
|
+
batchId: string;
|
|
150
|
+
total: number;
|
|
151
|
+
read: number;
|
|
152
|
+
written: number;
|
|
153
|
+
deferred: number;
|
|
154
|
+
targets: TenantNode[];
|
|
155
|
+
constructor(tid: string, name: string);
|
|
156
|
+
update(total: number, read: number, written: number, deferred: number): void;
|
|
157
|
+
}
|
|
158
|
+
export class APIResult {
|
|
159
|
+
result: boolean;
|
|
160
|
+
status: number;
|
|
161
|
+
error: string;
|
|
162
|
+
constructor();
|
|
163
|
+
}
|
|
164
|
+
//
|
|
165
|
+
// Azure AD Graph API
|
|
166
|
+
//
|
|
167
|
+
export function groupGet(tenant: Tenant, groupid: string): Promise<{group: string, error: string}>;
|
|
168
|
+
export function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{groups: Group[], error: string}>;
|
|
169
|
+
export function signIn(user: User, tasks: TaskArray): void;
|
|
170
|
+
export function signInIncrementally(user: User, scope: string): void;
|
|
171
|
+
export function signOut(user: User): void;
|
|
172
|
+
export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
|
|
173
|
+
export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
|
|
174
|
+
export function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): Promise<boolean>;
|
|
175
|
+
export function usersGet(tenant: Tenant): { users: string[], error: string };
|
|
176
|
+
//
|
|
177
|
+
// Mindline Config API
|
|
178
|
+
//
|
|
179
|
+
export function configEdit(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): APIResult;
|
|
180
|
+
export function configRemove(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): APIResult;
|
|
181
|
+
export function configsRefresh(instance: IPublicClientApplication, authorizedUser: User, workspaceId: string, ii: InitInfo, debug: boolean): APIResult;
|
|
182
|
+
export function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): APIResult;
|
|
183
|
+
export function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): APIResult;
|
|
184
|
+
export function tenantComplete(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, debug: boolean): APIResult;
|
|
185
|
+
export function tenantRemove(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string, debug: boolean): APIResult;
|
|
186
|
+
export function userAdd(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): APIResult;
|
|
183
187
|
export function userRemove(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): APIResult;
|
|
184
|
-
//
|
|
185
|
-
// Mindline Sync API
|
|
186
|
-
//
|
|
187
188
|
}
|
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,
|
|
@@ -106,6 +115,7 @@ export class TenantConfigInfo {
|
|
|
106
115
|
configurationTenantType: TenantConfigTypeStrings;
|
|
107
116
|
deltaToken: string;
|
|
108
117
|
filesWritten: number;
|
|
118
|
+
configId: string;
|
|
109
119
|
constructor() {
|
|
110
120
|
this.tid = "";
|
|
111
121
|
this.sourceGroupId = "";
|
|
@@ -113,6 +123,7 @@ export class TenantConfigInfo {
|
|
|
113
123
|
this.configurationTenantType = "source";
|
|
114
124
|
this.deltaToken = "";
|
|
115
125
|
this.filesWritten = 0;
|
|
126
|
+
this.configId = "";
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
export class Config {
|
|
@@ -531,134 +542,179 @@ export class BatchArray {
|
|
|
531
542
|
}
|
|
532
543
|
}
|
|
533
544
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
let
|
|
549
|
-
|
|
550
|
-
let
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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}`);
|
|
560
602
|
}
|
|
561
|
-
if (statskeys[j].
|
|
562
|
-
|
|
603
|
+
if (statskeys[j].endsWith("CurrentCount")) {
|
|
604
|
+
tenantNode.read = Number(statsvalues[j]);
|
|
605
|
+
console.log(`----- ${tenantNode.name} Currently Read: ${tenantNode.read}`);
|
|
563
606
|
}
|
|
564
607
|
}
|
|
565
|
-
if (statskeys[j].
|
|
566
|
-
|
|
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
|
+
}
|
|
567
684
|
}
|
|
568
|
-
statistics = statistics + statskeys[j] + "=" + statsvalues[j] + "<br />" //
|
|
569
|
-
}
|
|
570
|
-
updateProgress(total, currentR, currentW, currentD);
|
|
571
|
-
myFunction(item.TargetID, statistics);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
connection.start().catch(console.error);
|
|
575
|
-
|
|
576
|
-
function myFunction(targetid, str) {
|
|
577
|
-
const table = document.getElementById("the-table");
|
|
578
|
-
let row = document.getElementById(targetid);
|
|
579
|
-
if (row) {
|
|
580
|
-
row.cells[1].innerHTML = "<code>" + str + "</ code>";
|
|
581
|
-
} else {
|
|
582
|
-
row = table.insertRow(1);
|
|
583
|
-
row.id = targetid;
|
|
584
|
-
let cell1 = row.insertCell(0);
|
|
585
|
-
let cell2 = row.insertCell(1);
|
|
586
|
-
let caption = (targetid === "00000000-0000-0000-0000-000000000000") ? "<B>Status of ServiceBus QUEUES</B>" : targetid;
|
|
587
|
-
cell1.innerHTML = "<code>" + caption + "</ code>";
|
|
588
|
-
cell2.innerHTML = "<code>" + str + "</ code>";
|
|
589
685
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
function updateRead(total, current) {
|
|
598
|
-
const element = document.getElementById("readBar");
|
|
599
|
-
const width = Math.round(100 * current / total);
|
|
600
|
-
element.style.width = width + '%';
|
|
601
|
-
element.innerHTML = "R=" + width + '%';
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function updateWrite(total, w, d) {
|
|
605
|
-
const elementW = document.getElementById("writeBar");
|
|
606
|
-
const widthW = Math.round(100 * w / total);
|
|
607
|
-
elementW.style.width = widthW + '%';
|
|
608
|
-
elementW.innerHTML = "W=" + widthW + '%';
|
|
609
|
-
|
|
610
|
-
const elementD = document.getElementById("deferBar");
|
|
611
|
-
let width = 0;
|
|
612
|
-
let widthScaled = 0;
|
|
613
|
-
if (d > 0) {
|
|
614
|
-
width = Math.round(100 * d / total) + 1;
|
|
615
|
-
if (width < 10) {
|
|
616
|
-
widthScaled = width * 10;
|
|
617
|
-
}
|
|
618
|
-
else if (width < 20) {
|
|
619
|
-
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"`);
|
|
620
692
|
}
|
|
621
693
|
else {
|
|
622
|
-
|
|
694
|
+
setConfigSyncResult("writing in progress");
|
|
695
|
+
console.log(`Setting config sync result: "writing in progress"`);
|
|
623
696
|
}
|
|
624
697
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
elementD.style.width = widthScaled + '%';
|
|
629
|
-
elementD.innerHTML = width + '%';
|
|
630
|
-
document.getElementById("deferred").innerHTML = 'Deferred:' + width + '% (' + d + ' of ' + total + ')';
|
|
631
|
-
// cycle through desired states for sources and targets
|
|
632
|
-
if (this.tenantNodes != null) {
|
|
633
|
-
this.tenantNodes.map((sourceTenantNode: TenantNode) => {
|
|
634
|
-
if (sourceTenantNode.read == 0) sourceTenantNode.update(100, 50, 0, 0);
|
|
635
|
-
else if (sourceTenantNode.read == 50) sourceTenantNode.update(100, 100, 0, 0);
|
|
636
|
-
else sourceTenantNode.update(0, 0, 0, 0);
|
|
637
|
-
if (sourceTenantNode.targets != null) {
|
|
638
|
-
sourceTenantNode.targets.map((targetTenantNode: TenantNode) => {
|
|
639
|
-
if (targetTenantNode.written == 0) targetTenantNode.update(100, 0, 50, 0);
|
|
640
|
-
else if (targetTenantNode.written == 50) targetTenantNode.update(100, 0, 100, 0);
|
|
641
|
-
else if (targetTenantNode.written == 100) targetTenantNode.update(100, 0, 99, 1);
|
|
642
|
-
else targetTenantNode.update(0, 0, 0, 0);
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
});
|
|
698
|
+
else {
|
|
699
|
+
setConfigSyncResult("reading in progress");
|
|
700
|
+
console.log(`Setting config sync result: "reading in progress"`);
|
|
646
701
|
}
|
|
647
702
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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);
|
|
714
|
+
});
|
|
656
715
|
// execute post to reader endpoint
|
|
657
|
-
readerPost(instance, authorizedUser, config);
|
|
658
|
-
|
|
659
|
-
// start SignalR connection
|
|
660
|
-
//debugger;
|
|
661
|
-
//monitorSyncProgress();
|
|
716
|
+
result = await readerPost(instance, authorizedUser, config);
|
|
717
|
+
return result;
|
|
662
718
|
}
|
|
663
719
|
}
|
|
664
720
|
export class TenantNode {
|
|
@@ -666,6 +722,7 @@ export class TenantNode {
|
|
|
666
722
|
status: string;
|
|
667
723
|
name: string;
|
|
668
724
|
tid: string;
|
|
725
|
+
batchId: string;
|
|
669
726
|
total: number;
|
|
670
727
|
read: number;
|
|
671
728
|
written: number;
|
|
@@ -675,6 +732,7 @@ export class TenantNode {
|
|
|
675
732
|
this.expanded = false;
|
|
676
733
|
this.name = name;
|
|
677
734
|
this.tid = tid;
|
|
735
|
+
this.batchId = "";
|
|
678
736
|
this.targets = new Array<TenantNode>();
|
|
679
737
|
this.update(0, 0, 0, 0);
|
|
680
738
|
}
|
|
@@ -806,9 +864,9 @@ export function signOut(user: User): void {
|
|
|
806
864
|
export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): Promise<boolean> {
|
|
807
865
|
if (debug) debugger;
|
|
808
866
|
// do we already have a valid tenant name? if so, nothing to add
|
|
809
|
-
if (
|
|
867
|
+
if (tenant.name != null && tenant.name !== "") return false;
|
|
810
868
|
// if needed, retrieve and cache access token
|
|
811
|
-
if (
|
|
869
|
+
if (loggedInUser.accessToken != null && loggedInUser.accessToken === "") {
|
|
812
870
|
console.log(`tenantRelationshipsGetByDomain called with invalid logged in user: ${loggedInUser.name}`);
|
|
813
871
|
try {
|
|
814
872
|
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
|
|
@@ -828,29 +886,32 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
|
|
|
828
886
|
// make tenant endpoint call
|
|
829
887
|
try {
|
|
830
888
|
// create tenant info endpoint
|
|
831
|
-
var tenantEndpoint = graphConfig.
|
|
889
|
+
var tenantEndpoint = getGraphEndpoint(tenant.authority) + graphConfig.graphTenantByDomainPredicate;
|
|
832
890
|
tenantEndpoint += "(domainName='";
|
|
833
891
|
tenantEndpoint += tenant.domain;
|
|
834
892
|
tenantEndpoint += "')";
|
|
835
893
|
console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
|
|
836
894
|
let response = await fetch(tenantEndpoint, options);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
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
|
+
}
|
|
842
910
|
}
|
|
843
|
-
else
|
|
844
|
-
|
|
845
|
-
tenant.tid = data.tenantId;
|
|
846
|
-
tenant.name = data.displayName;
|
|
847
|
-
console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
|
|
848
|
-
return true; // success, need UX to re-render
|
|
911
|
+
else {
|
|
912
|
+
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
849
913
|
}
|
|
850
914
|
}
|
|
851
|
-
else {
|
|
852
|
-
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
853
|
-
}
|
|
854
915
|
}
|
|
855
916
|
catch (error: any) {
|
|
856
917
|
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
@@ -862,9 +923,9 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
|
|
|
862
923
|
export async function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): Promise<boolean> {
|
|
863
924
|
if (debug) debugger;
|
|
864
925
|
// do we already have a valid company name? if so, nothing to add, no need for UX to re-render
|
|
865
|
-
if (
|
|
926
|
+
if (user.companyName != "") return false;
|
|
866
927
|
// if needed, retrieve and cache access token
|
|
867
|
-
if (
|
|
928
|
+
if (user.accessToken === "") {
|
|
868
929
|
try {
|
|
869
930
|
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
|
|
870
931
|
user.accessToken = response.accessToken; // cache access token
|
|
@@ -883,7 +944,7 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
883
944
|
// make tenant endpoint call
|
|
884
945
|
try {
|
|
885
946
|
// create tenant info endpoint
|
|
886
|
-
var tenantEndpoint = graphConfig.
|
|
947
|
+
var tenantEndpoint = getGraphEndpoint(user.authority) + graphConfig.graphTenantByIdPredicate;
|
|
887
948
|
tenantEndpoint += "(tenantId='";
|
|
888
949
|
tenantEndpoint += user.tid;
|
|
889
950
|
tenantEndpoint += "')";
|
|
@@ -922,6 +983,54 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
922
983
|
tasks.setTaskEnd("GET tenant details", new Date(), "failed");
|
|
923
984
|
return false; // failed, no need for UX to re-render
|
|
924
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
|
+
}
|
|
925
1034
|
//usersGet - GET from AAD Users endpoint
|
|
926
1035
|
export async function usersGet(tenant: Tenant): Promise<{ users: string[], error: string }> {
|
|
927
1036
|
// need a read or write access token to get graph users
|
|
@@ -970,26 +1079,77 @@ export async function configEdit(instance: IPublicClientApplication, authorizedU
|
|
|
970
1079
|
export async function configRemove(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
971
1080
|
return configDelete(instance, authorizedUser, config, workspaceId, debug);
|
|
972
1081
|
}
|
|
1082
|
+
export async function configsRefresh(instance: IPublicClientApplication, authorizedUser: User, workspaceId: string, ii: InitInfo, debug: boolean): Promise<APIResult> {
|
|
1083
|
+
let result: APIResult = new APIResult();
|
|
1084
|
+
if (debug) debugger;
|
|
1085
|
+
try {
|
|
1086
|
+
let workspace: Workspace = ii.ws.find((w) => w.id === workspaceId);
|
|
1087
|
+
if (workspace != null) {
|
|
1088
|
+
// clear Config associations as we are about to reset
|
|
1089
|
+
workspace.associatedConfigs.length = 0;
|
|
1090
|
+
// GET configs associated with this workspace
|
|
1091
|
+
let configsPromise: Promise<APIResult> = configsGet(instance, authorizedUser, workspace.id, debug);
|
|
1092
|
+
// wait for query to finish, return on any failure
|
|
1093
|
+
let [configsResult] = await Promise.all([configsPromise]);
|
|
1094
|
+
if (!configsResult.result) return configsResult;
|
|
1095
|
+
// process returned workspace components
|
|
1096
|
+
processReturnedConfigs(workspace, ii, configsResult.array!);
|
|
1097
|
+
// tag components with workspaceIDs
|
|
1098
|
+
ii.tagWithWorkspaces();
|
|
1099
|
+
}
|
|
1100
|
+
return result;
|
|
1101
|
+
}
|
|
1102
|
+
catch (error: any) {
|
|
1103
|
+
console.log(error.message);
|
|
1104
|
+
result.error = error.message;
|
|
1105
|
+
}
|
|
1106
|
+
result.result = false;
|
|
1107
|
+
result.status = 500;
|
|
1108
|
+
return result;
|
|
1109
|
+
}
|
|
973
1110
|
// retrieve Workspace(s), User(s), Tenant(s), Config(s) given newly logged in user
|
|
974
1111
|
export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult> {
|
|
975
1112
|
let result: APIResult = new APIResult();
|
|
976
1113
|
if (debug) debugger;
|
|
977
|
-
//
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
+
}
|
|
984
1147
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
result
|
|
989
|
-
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;
|
|
990
1152
|
}
|
|
991
|
-
if (result.result) result.error = version;
|
|
992
|
-
return result;
|
|
993
1153
|
}
|
|
994
1154
|
export async function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): Promise<APIResult> {
|
|
995
1155
|
return tenantPost(instance, authorizedUser, tenant, workspaceId);
|
|
@@ -1053,7 +1213,7 @@ function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTena
|
|
|
1053
1213
|
// clear and overwrite dummy
|
|
1054
1214
|
tenant = ii.ts.at(dummyIndex);
|
|
1055
1215
|
} else {
|
|
1056
|
-
// create and track new
|
|
1216
|
+
// create and track new tenant
|
|
1057
1217
|
tenant = new Tenant();
|
|
1058
1218
|
ii.ts.push(tenant);
|
|
1059
1219
|
}
|
|
@@ -1109,6 +1269,7 @@ function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConf
|
|
|
1109
1269
|
tenantConfigInfo.sourceGroupName = tci.sourceGroupName;
|
|
1110
1270
|
tenantConfigInfo.configurationTenantType = tci.configurationTenantType.toLowerCase();
|
|
1111
1271
|
tenantConfigInfo.deltaToken = tci.deltaToken ?? "";
|
|
1272
|
+
tenantConfigInfo.configId = config!.id;
|
|
1112
1273
|
config!.tenants.push(tenantConfigInfo);
|
|
1113
1274
|
});
|
|
1114
1275
|
// ensure this workspace tracks this config
|
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,9 +13,13 @@
|
|
|
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": {
|
|
22
|
+
"@microsoft/signalr": "^7.0.10",
|
|
19
23
|
"class-transformer": "^0.5.1",
|
|
20
24
|
"reflect-metadata": "^0.1.13"
|
|
21
25
|
}
|
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
|