@weavy/uikit-react 18.0.1 → 18.0.2
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/changelog.md +7 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/client/WeavyClient.d.ts +4 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/client/WeavyClient.d.ts +4 -2
- package/dist/index.d.ts +3 -1
- package/package.json +1 -1
- package/src/client/WeavyClient.ts +235 -180
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IWeavyClient, WeavyClientOptions } from
|
|
1
|
+
import { IWeavyClient, WeavyClientOptions } from "../types/types";
|
|
2
2
|
export default class WeavyClient implements IWeavyClient {
|
|
3
3
|
url: string;
|
|
4
4
|
connection: import("@microsoft/signalr").HubConnection;
|
|
@@ -6,6 +6,7 @@ export default class WeavyClient implements IWeavyClient {
|
|
|
6
6
|
groups: string[];
|
|
7
7
|
connectionEvents: any[];
|
|
8
8
|
isConnectionStarted: any;
|
|
9
|
+
private signalRAccessTokenRefresh;
|
|
9
10
|
token: string;
|
|
10
11
|
tokenPromise: Promise<string> | null;
|
|
11
12
|
EVENT_NAMESPACE: string;
|
|
@@ -13,11 +14,12 @@ export default class WeavyClient implements IWeavyClient {
|
|
|
13
14
|
EVENT_RECONNECTING: string;
|
|
14
15
|
EVENT_RECONNECTED: string;
|
|
15
16
|
constructor(options: WeavyClientOptions);
|
|
17
|
+
connect(): Promise<void>;
|
|
16
18
|
get(url: string, retry?: boolean): Promise<Response>;
|
|
17
19
|
post(url: string, method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", body: string | FormData, contentType?: string, retry?: boolean): Promise<Response>;
|
|
18
20
|
upload(url: string, method: "POST" | "PUT" | "PATCH", body: string | FormData, contentType?: string, onProgress?: (progress: number) => void, retry?: boolean): Promise<Response>;
|
|
19
21
|
getToken(refresh: boolean): Promise<string>;
|
|
20
|
-
tokenFactoryInternal(refresh?: boolean
|
|
22
|
+
tokenFactoryInternal(refresh?: boolean): Promise<string>;
|
|
21
23
|
subscribe(group: string, event: string, callback: any): Promise<void>;
|
|
22
24
|
unsubscribe(group: string, event: string, callback: any): Promise<void>;
|
|
23
25
|
destroy(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ declare class WeavyClient implements IWeavyClient {
|
|
|
68
68
|
groups: string[];
|
|
69
69
|
connectionEvents: any[];
|
|
70
70
|
isConnectionStarted: any;
|
|
71
|
+
private signalRAccessTokenRefresh;
|
|
71
72
|
token: string;
|
|
72
73
|
tokenPromise: Promise<string> | null;
|
|
73
74
|
EVENT_NAMESPACE: string;
|
|
@@ -75,11 +76,12 @@ declare class WeavyClient implements IWeavyClient {
|
|
|
75
76
|
EVENT_RECONNECTING: string;
|
|
76
77
|
EVENT_RECONNECTED: string;
|
|
77
78
|
constructor(options: WeavyClientOptions);
|
|
79
|
+
connect(): Promise<void>;
|
|
78
80
|
get(url: string, retry?: boolean): Promise<Response>;
|
|
79
81
|
post(url: string, method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", body: string | FormData, contentType?: string, retry?: boolean): Promise<Response>;
|
|
80
82
|
upload(url: string, method: "POST" | "PUT" | "PATCH", body: string | FormData, contentType?: string, onProgress?: (progress: number) => void, retry?: boolean): Promise<Response>;
|
|
81
83
|
getToken(refresh: boolean): Promise<string>;
|
|
82
|
-
tokenFactoryInternal(refresh?: boolean
|
|
84
|
+
tokenFactoryInternal(refresh?: boolean): Promise<string>;
|
|
83
85
|
subscribe(group: string, event: string, callback: any): Promise<void>;
|
|
84
86
|
unsubscribe(group: string, event: string, callback: any): Promise<void>;
|
|
85
87
|
destroy(): void;
|
package/package.json
CHANGED
|
@@ -1,206 +1,261 @@
|
|
|
1
|
-
import { HubConnectionBuilder, LogLevel } from
|
|
2
|
-
import { IWeavyClient, WeavyClientOptions } from
|
|
1
|
+
import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr";
|
|
2
|
+
import { IWeavyClient, WeavyClientOptions } from "../types/types";
|
|
3
3
|
|
|
4
4
|
export default class WeavyClient implements IWeavyClient {
|
|
5
|
-
|
|
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
|
-
|
|
5
|
+
url;
|
|
6
|
+
connection;
|
|
7
|
+
tokenFactory;
|
|
8
|
+
groups: string[] = [];
|
|
9
|
+
connectionEvents: any[] = [];
|
|
10
|
+
isConnectionStarted: any;
|
|
11
|
+
private signalRAccessTokenRefresh = false;
|
|
12
|
+
|
|
13
|
+
token: string = "";
|
|
14
|
+
tokenPromise: Promise<string> | null;
|
|
15
|
+
EVENT_NAMESPACE = ".connection";
|
|
16
|
+
EVENT_CLOSE = "close";
|
|
17
|
+
EVENT_RECONNECTING = "reconnecting";
|
|
18
|
+
EVENT_RECONNECTED = "reconnected";
|
|
19
|
+
|
|
20
|
+
constructor(options: WeavyClientOptions) {
|
|
21
|
+
this.url = options.url;
|
|
22
|
+
this.tokenFactory = options.tokenFactory;
|
|
23
|
+
this.tokenPromise = null;
|
|
24
|
+
this.connection = new HubConnectionBuilder()
|
|
25
|
+
.configureLogging(LogLevel.None)
|
|
26
|
+
.withUrl(this.url + "/hubs/rtm", {
|
|
27
|
+
accessTokenFactory: async () => {
|
|
28
|
+
if (this.signalRAccessTokenRefresh) {
|
|
29
|
+
console.error("SignalR retrying with refreshed token.");
|
|
30
|
+
const token = await this.tokenFactoryInternal(true);
|
|
31
|
+
this.signalRAccessTokenRefresh = false;
|
|
32
|
+
return token;
|
|
33
|
+
} else {
|
|
34
|
+
//console.error("first attempt")
|
|
35
|
+
const token = await this.tokenFactoryInternal();
|
|
36
|
+
return token;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
.withAutomaticReconnect()
|
|
41
|
+
.build();
|
|
42
|
+
|
|
43
|
+
this.connect();
|
|
44
|
+
|
|
45
|
+
this.connection.onclose((error) =>
|
|
46
|
+
this.triggerHandler(this.EVENT_CLOSE, error)
|
|
47
|
+
);
|
|
48
|
+
this.connection.onreconnecting((error) =>
|
|
49
|
+
this.triggerHandler(this.EVENT_RECONNECTING, error)
|
|
50
|
+
);
|
|
51
|
+
this.connection.onreconnected((connectionId) =>
|
|
52
|
+
this.triggerHandler(this.EVENT_RECONNECTED, connectionId)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async connect() {
|
|
57
|
+
try {
|
|
58
|
+
this.isConnectionStarted = this.connection.start();
|
|
59
|
+
await this.isConnectionStarted;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.log("Could not start SignalR.");
|
|
62
|
+
if (!this.signalRAccessTokenRefresh) {
|
|
63
|
+
this.signalRAccessTokenRefresh = true;
|
|
31
64
|
this.isConnectionStarted = this.connection.start();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.connection.onreconnecting(error => this.triggerHandler(this.EVENT_RECONNECTING, error));
|
|
35
|
-
this.connection.onreconnected(connectionId => this.triggerHandler(this.EVENT_RECONNECTED, connectionId));
|
|
36
|
-
|
|
65
|
+
await this.isConnectionStarted;
|
|
66
|
+
}
|
|
37
67
|
}
|
|
38
68
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
this.signalRAccessTokenRefresh = false;
|
|
70
|
+
console.log("SignalR connected.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async get(url: string, retry: boolean = true): Promise<Response> {
|
|
74
|
+
//const token = await this.tokenFactoryInternal();
|
|
75
|
+
//console.log("GET:", url, " - t:", token);
|
|
76
|
+
const response = await fetch(this.url + url, {
|
|
77
|
+
headers: {
|
|
78
|
+
"content-type": "application/json",
|
|
79
|
+
Authorization: "Bearer " + (await this.tokenFactoryInternal()),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
if ((response.status === 401 || response.status === 403) && retry) {
|
|
85
|
+
await this.tokenFactoryInternal(true);
|
|
86
|
+
return await this.get(url, false);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.error(`Error calling endpoint ${url}`, response);
|
|
59
90
|
}
|
|
60
91
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
if ((response.status === 401 || response.status === 403) && retry) {
|
|
77
|
-
await this.tokenFactoryInternal(true);
|
|
78
|
-
return await this.post(url, method, body, contentType, false);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
//console.error(`Error calling endpoint ${url}`, response)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return response;
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async post(
|
|
96
|
+
url: string,
|
|
97
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
|
98
|
+
body: string | FormData,
|
|
99
|
+
contentType: string = "application/json",
|
|
100
|
+
retry: boolean = true
|
|
101
|
+
): Promise<Response> {
|
|
102
|
+
let headers: HeadersInit = {
|
|
103
|
+
Authorization: "Bearer " + (await this.tokenFactoryInternal()),
|
|
85
104
|
};
|
|
86
105
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const client = this as WeavyClient;
|
|
90
|
-
const token = await client.tokenFactoryInternal();
|
|
91
|
-
|
|
92
|
-
return await new Promise<Response>(function (resolve, reject) {
|
|
93
|
-
// XMLHttpRequest instead of fetch because we want to track progress
|
|
94
|
-
let xhr = new XMLHttpRequest();
|
|
95
|
-
xhr.open(method, client.url + url, true);
|
|
96
|
-
xhr.setRequestHeader("Authorization", "Bearer " + token)
|
|
97
|
-
if (contentType !== ""){
|
|
98
|
-
xhr.setRequestHeader("content-type", contentType);
|
|
99
|
-
}
|
|
100
|
-
if (onProgress) {
|
|
101
|
-
xhr.upload.addEventListener("progress", (e: ProgressEvent<EventTarget>) => {
|
|
102
|
-
onProgress((e.loaded / e.total) * 100 || 100)
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
xhr.onload = (evt: ProgressEvent<EventTarget>) => {
|
|
107
|
-
if (retry && (xhr.status === 401 || xhr.status === 401)) {
|
|
108
|
-
client.tokenFactoryInternal(true)
|
|
109
|
-
.then(() => client.upload(url, method, body, contentType, onProgress, false))
|
|
110
|
-
.then(resolve)
|
|
111
|
-
.catch(reject);
|
|
112
|
-
} else {
|
|
113
|
-
resolve(new Response(xhr.response, { status: xhr.status, statusText: xhr.statusText }));
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
xhr.onerror = reject;
|
|
117
|
-
xhr.send(body);
|
|
118
|
-
});
|
|
106
|
+
if (contentType !== "") {
|
|
107
|
+
headers["content-type"] = contentType;
|
|
119
108
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
const response = await fetch(this.url + url, {
|
|
110
|
+
method: method,
|
|
111
|
+
body: body,
|
|
112
|
+
headers: headers,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
if ((response.status === 401 || response.status === 403) && retry) {
|
|
117
|
+
await this.tokenFactoryInternal(true);
|
|
118
|
+
return await this.post(url, method, body, contentType, false);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//console.error(`Error calling endpoint ${url}`, response)
|
|
126
122
|
}
|
|
127
123
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async upload(
|
|
128
|
+
url: string,
|
|
129
|
+
method: "POST" | "PUT" | "PATCH",
|
|
130
|
+
body: string | FormData,
|
|
131
|
+
contentType: string = "application/json",
|
|
132
|
+
onProgress?: (progress: number) => void,
|
|
133
|
+
retry: boolean = true
|
|
134
|
+
) {
|
|
135
|
+
const client = this as WeavyClient;
|
|
136
|
+
const token = await client.tokenFactoryInternal();
|
|
137
|
+
|
|
138
|
+
return await new Promise<Response>(function (resolve, reject) {
|
|
139
|
+
// XMLHttpRequest instead of fetch because we want to track progress
|
|
140
|
+
let xhr = new XMLHttpRequest();
|
|
141
|
+
xhr.open(method, client.url + url, true);
|
|
142
|
+
xhr.setRequestHeader("Authorization", "Bearer " + token);
|
|
143
|
+
if (contentType !== "") {
|
|
144
|
+
xhr.setRequestHeader("content-type", contentType);
|
|
145
|
+
}
|
|
146
|
+
if (onProgress) {
|
|
147
|
+
xhr.upload.addEventListener(
|
|
148
|
+
"progress",
|
|
149
|
+
(e: ProgressEvent<EventTarget>) => {
|
|
150
|
+
onProgress((e.loaded / e.total) * 100 || 100);
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
xhr.onload = (evt: ProgressEvent<EventTarget>) => {
|
|
156
|
+
if (retry && (xhr.status === 401 || xhr.status === 401)) {
|
|
157
|
+
client
|
|
158
|
+
.tokenFactoryInternal(true)
|
|
159
|
+
.then(() =>
|
|
160
|
+
client.upload(url, method, body, contentType, onProgress, false)
|
|
161
|
+
)
|
|
162
|
+
.then(resolve)
|
|
163
|
+
.catch(reject);
|
|
164
|
+
} else {
|
|
165
|
+
resolve(
|
|
166
|
+
new Response(xhr.response, {
|
|
167
|
+
status: xhr.status,
|
|
168
|
+
statusText: xhr.statusText,
|
|
169
|
+
})
|
|
170
|
+
);
|
|
143
171
|
}
|
|
172
|
+
};
|
|
173
|
+
xhr.onerror = reject;
|
|
174
|
+
xhr.send(body);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async getToken(refresh: boolean) {
|
|
179
|
+
if (!this.token || refresh) {
|
|
180
|
+
this.token = await this.tokenFactory(true);
|
|
144
181
|
}
|
|
182
|
+
return this.token;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async tokenFactoryInternal(refresh: boolean = false): Promise<string> {
|
|
186
|
+
if (this.token && !refresh) return this.token;
|
|
187
|
+
|
|
188
|
+
if (!this.tokenPromise) {
|
|
189
|
+
this.tokenPromise = this.tokenFactory(refresh);
|
|
190
|
+
let token = await this.tokenPromise;
|
|
191
|
+
|
|
192
|
+
this.tokenPromise = null;
|
|
193
|
+
this.token = token;
|
|
194
|
+
return this.token;
|
|
195
|
+
} else {
|
|
196
|
+
//console.log("Already a promise in action, wait for it to resolve...")
|
|
197
|
+
return this.tokenPromise;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async subscribe(group: string, event: string, callback: any) {
|
|
202
|
+
await this.isConnectionStarted;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
var name = group ? group + ":" + event : event;
|
|
206
|
+
await this.connection.invoke("Subscribe", name);
|
|
207
|
+
this.groups.push(name);
|
|
208
|
+
this.connection.on(name, callback);
|
|
209
|
+
} catch (err: any) {
|
|
210
|
+
console.warn("Error in Subscribe:", err);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
145
213
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
var name = group ? group + ":" + event : event;
|
|
151
|
-
await this.connection.invoke("Subscribe", name);
|
|
152
|
-
this.groups.push(name);
|
|
153
|
-
this.connection.on(name, callback);
|
|
154
|
-
} catch (err: any) {
|
|
155
|
-
console.warn("Error in Subscribe:", err)
|
|
156
|
-
}
|
|
214
|
+
async unsubscribe(group: string, event: string, callback: any) {
|
|
215
|
+
await this.isConnectionStarted;
|
|
216
|
+
var name = group ? group + ":" + event : event;
|
|
157
217
|
|
|
158
|
-
|
|
218
|
+
// get first occurence of group name and remove it
|
|
219
|
+
const index = this.groups.findIndex((e) => e === name);
|
|
220
|
+
if (index !== -1) {
|
|
221
|
+
this.groups = this.groups.splice(index, 1);
|
|
159
222
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// get first occurence of group name and remove it
|
|
165
|
-
const index = this.groups.findIndex(e => e === name);
|
|
166
|
-
if (index !== -1) {
|
|
167
|
-
this.groups = this.groups.splice(index, 1);
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
// if no more groups, remove from server
|
|
171
|
-
if (!this.groups.find(e => e === name)) {
|
|
172
|
-
await this.connection.invoke("Unsubscribe", name);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
} catch (err: any) {
|
|
176
|
-
console.warn("Error in Unsubscribe:", err)
|
|
177
|
-
}
|
|
223
|
+
try {
|
|
224
|
+
// if no more groups, remove from server
|
|
225
|
+
if (!this.groups.find((e) => e === name)) {
|
|
226
|
+
await this.connection.invoke("Unsubscribe", name);
|
|
178
227
|
}
|
|
179
|
-
|
|
180
|
-
|
|
228
|
+
} catch (err: any) {
|
|
229
|
+
console.warn("Error in Unsubscribe:", err);
|
|
230
|
+
}
|
|
181
231
|
}
|
|
182
232
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
233
|
+
this.connection.off(name, callback);
|
|
234
|
+
}
|
|
186
235
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
236
|
+
destroy() {
|
|
237
|
+
this.connection.stop();
|
|
238
|
+
}
|
|
190
239
|
|
|
191
|
-
|
|
240
|
+
triggerHandler(name: string, ...data: any) {
|
|
241
|
+
name = name.endsWith(this.EVENT_NAMESPACE)
|
|
242
|
+
? name
|
|
243
|
+
: name + this.EVENT_NAMESPACE;
|
|
244
|
+
let event = new CustomEvent(name, { cancelable: false });
|
|
192
245
|
|
|
193
|
-
|
|
194
|
-
if (eventHandler.name === name) {
|
|
195
|
-
eventHandler.handler(event, ...data);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
246
|
+
console.debug("triggerHandler", name);
|
|
198
247
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
248
|
+
this.connectionEvents.forEach((eventHandler) => {
|
|
249
|
+
if (eventHandler.name === name) {
|
|
250
|
+
eventHandler.handler(event, ...data);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (name === this.EVENT_RECONNECTED + this.EVENT_NAMESPACE) {
|
|
255
|
+
// re-add to signalr groups after reconnect
|
|
256
|
+
for (var i = 0; i < this.groups.length; i++) {
|
|
257
|
+
this.connection.invoke("Subscribe", this.groups[i]);
|
|
258
|
+
}
|
|
205
259
|
}
|
|
260
|
+
}
|
|
206
261
|
}
|