@kwiz/common 1.0.78 → 1.0.80

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.
Files changed (118) hide show
  1. package/.github/workflows/npm-publish.yml +24 -0
  2. package/.madgerc +2 -2
  3. package/LICENSE +21 -21
  4. package/fix-folder-imports.js +26 -26
  5. package/lib/cjs/helpers/sharepoint.js +5 -1
  6. package/lib/cjs/helpers/sharepoint.js.map +1 -1
  7. package/lib/cjs/helpers/typecheckers.js +5 -1
  8. package/lib/cjs/helpers/typecheckers.js.map +1 -1
  9. package/lib/cjs/types/libs/msal.types.js +26 -26
  10. package/lib/cjs/utils/sharepoint.rest/list.js +1 -1
  11. package/lib/cjs/utils/sharepoint.rest/list.js.map +1 -1
  12. package/lib/cjs/utils/sharepoint.rest/user.js +11 -11
  13. package/lib/esm/helpers/sharepoint.js +3 -0
  14. package/lib/esm/helpers/sharepoint.js.map +1 -1
  15. package/lib/esm/helpers/typecheckers.js +3 -0
  16. package/lib/esm/helpers/typecheckers.js.map +1 -1
  17. package/lib/esm/types/libs/msal.types.js +26 -26
  18. package/lib/esm/utils/sharepoint.rest/list.js +2 -2
  19. package/lib/esm/utils/sharepoint.rest/list.js.map +1 -1
  20. package/lib/esm/utils/sharepoint.rest/user.js +11 -11
  21. package/lib/types/helpers/sharepoint.d.ts +1 -0
  22. package/lib/types/helpers/typecheckers.d.ts +1 -0
  23. package/package.json +77 -77
  24. package/readme.md +17 -17
  25. package/src/_dependencies.ts +12 -12
  26. package/src/config.ts +17 -17
  27. package/src/helpers/Guid.ts +181 -181
  28. package/src/helpers/base64.ts +173 -173
  29. package/src/helpers/browser.test.js +13 -13
  30. package/src/helpers/browser.ts +1348 -1348
  31. package/src/helpers/browserinfo.ts +292 -292
  32. package/src/helpers/collections.base.test.js +25 -25
  33. package/src/helpers/collections.base.ts +437 -437
  34. package/src/helpers/collections.ts +107 -107
  35. package/src/helpers/color.ts +54 -54
  36. package/src/helpers/cookies.ts +59 -59
  37. package/src/helpers/date.test.js +119 -119
  38. package/src/helpers/date.ts +188 -188
  39. package/src/helpers/debug.ts +186 -186
  40. package/src/helpers/emails.ts +6 -6
  41. package/src/helpers/eval.ts +5 -5
  42. package/src/helpers/file.test.js +50 -50
  43. package/src/helpers/file.ts +58 -58
  44. package/src/helpers/flatted.ts +149 -149
  45. package/src/helpers/functions.ts +16 -16
  46. package/src/helpers/graph/calendar.types.ts +10 -10
  47. package/src/helpers/http.ts +69 -69
  48. package/src/helpers/images.ts +22 -22
  49. package/src/helpers/json.ts +38 -38
  50. package/src/helpers/md5.ts +189 -189
  51. package/src/helpers/objects.test.js +33 -33
  52. package/src/helpers/objects.ts +270 -270
  53. package/src/helpers/promises.test.js +37 -37
  54. package/src/helpers/promises.ts +165 -165
  55. package/src/helpers/random.ts +27 -27
  56. package/src/helpers/scheduler/scheduler.test.js +103 -103
  57. package/src/helpers/scheduler/scheduler.ts +131 -131
  58. package/src/helpers/sharepoint.ts +776 -772
  59. package/src/helpers/strings.test.js +101 -101
  60. package/src/helpers/strings.ts +317 -317
  61. package/src/helpers/typecheckers.test.js +34 -34
  62. package/src/helpers/typecheckers.ts +266 -262
  63. package/src/helpers/url.test.js +43 -43
  64. package/src/helpers/url.ts +207 -207
  65. package/src/helpers/urlhelper.ts +111 -111
  66. package/src/index.ts +6 -6
  67. package/src/types/auth.ts +54 -54
  68. package/src/types/common.types.ts +15 -15
  69. package/src/types/flatted.types.ts +59 -59
  70. package/src/types/globals.types.ts +6 -6
  71. package/src/types/graph/calendar.types.ts +80 -80
  72. package/src/types/knownscript.types.ts +18 -18
  73. package/src/types/libs/datajs.types.ts +28 -28
  74. package/src/types/libs/ics.types.ts +30 -30
  75. package/src/types/libs/msal.types.ts +49 -49
  76. package/src/types/locales.ts +124 -124
  77. package/src/types/localstoragecache.types.ts +8 -8
  78. package/src/types/location.types.ts +27 -27
  79. package/src/types/moment.ts +11 -11
  80. package/src/types/regex.types.ts +16 -16
  81. package/src/types/rest.types.ts +95 -95
  82. package/src/types/sharepoint.types.ts +1465 -1465
  83. package/src/types/sharepoint.utils.types.ts +287 -287
  84. package/src/utils/auth/common.ts +74 -74
  85. package/src/utils/auth/discovery.test.js +12 -12
  86. package/src/utils/auth/discovery.ts +132 -132
  87. package/src/utils/base64.ts +27 -27
  88. package/src/utils/consolelogger.ts +320 -320
  89. package/src/utils/date.ts +35 -35
  90. package/src/utils/emails.ts +24 -24
  91. package/src/utils/knownscript.ts +286 -286
  92. package/src/utils/localstoragecache.ts +441 -441
  93. package/src/utils/rest.ts +501 -501
  94. package/src/utils/script.ts +170 -170
  95. package/src/utils/sharepoint.rest/common.ts +154 -154
  96. package/src/utils/sharepoint.rest/date.ts +62 -62
  97. package/src/utils/sharepoint.rest/file.folder.ts +598 -598
  98. package/src/utils/sharepoint.rest/item.ts +547 -547
  99. package/src/utils/sharepoint.rest/list.ts +1388 -1388
  100. package/src/utils/sharepoint.rest/listutils/GetListItemsByCaml.ts +774 -774
  101. package/src/utils/sharepoint.rest/listutils/GetListItemsById.ts +275 -275
  102. package/src/utils/sharepoint.rest/listutils/common.ts +206 -206
  103. package/src/utils/sharepoint.rest/location.ts +141 -141
  104. package/src/utils/sharepoint.rest/navigation-links.ts +86 -86
  105. package/src/utils/sharepoint.rest/user-search.ts +252 -252
  106. package/src/utils/sharepoint.rest/user.ts +491 -491
  107. package/src/utils/sharepoint.rest/web.ts +1384 -1384
  108. package/src/utils/sod.ts +194 -194
  109. package/lib/cjs/helpers/_dependencies.js +0 -21
  110. package/lib/cjs/helpers/_dependencies.js.map +0 -1
  111. package/lib/cjs/utils/_dependencies.js +0 -24
  112. package/lib/cjs/utils/_dependencies.js.map +0 -1
  113. package/lib/esm/helpers/_dependencies.js +0 -3
  114. package/lib/esm/helpers/_dependencies.js.map +0 -1
  115. package/lib/esm/utils/_dependencies.js +0 -4
  116. package/lib/esm/utils/_dependencies.js.map +0 -1
  117. package/lib/types/helpers/_dependencies.d.ts +0 -2
  118. package/lib/types/utils/_dependencies.d.ts +0 -3
package/src/utils/rest.ts CHANGED
@@ -1,502 +1,502 @@
1
- import { jsonParse } from "../helpers/json";
2
- import { assign, getGlobal, hasOwnProperty, jsonClone } from "../helpers/objects";
3
- import { isNullOrEmptyString, isNullOrUndefined, isNumber, isObject, isPrimitiveValue, isString } from "../helpers/typecheckers";
4
- import { IDictionary } from "../types/common.types";
5
- import { AllRestCacheOptionsKeys, IJsonSyncResult, IRequestBody, IRequestObjects, IRestCacheOptions, IRestError, IRestOptions, IRestRequestOptions, jsonTypes } from "../types/rest.types";
6
- import { ConsoleLogger } from "./consolelogger";
7
- import { getCacheItem, setCacheItem } from "./localstoragecache";
8
- import { getFormDigest } from "./sharepoint.rest/web";
9
-
10
- var logger = ConsoleLogger.get("kwizcom.rest.module");
11
- const supressDebugMessages = true;
12
-
13
- /** cache for 1 day */
14
- export const noLocalCache: IRestOptions = { allowCache: false };
15
- /** cache for 1 days */
16
- export const longLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 1 } };
17
- /** cache for 2 days */
18
- export const extraLongLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 2 } };
19
- /** cache for 7 days */
20
- export const weeekLongLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 7 } };
21
- /** cache for 30 days */
22
- export const monthLongLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 30 } };
23
- /** cache for 5 minutes */
24
- export const shortLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { minutes: 5 } };
25
- /** cache for 15 minutes */
26
- export const mediumLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { minutes: 15 } };
27
-
28
- interface IPendingRequest<T> {
29
- objects: IRequestObjects;
30
- /**the main promise */
31
- promise: Promise<T>;
32
- /**additional listeners
33
- * must add a separate promise, so that I can make a full(not shallow) copy of the result.
34
- * this way if the first caller changes the object, the second caller gets it unchanged.
35
- */
36
- listeners: {
37
- resolve: (result: T) => void;
38
- reject: (reason: any) => void;
39
- }[];
40
- }
41
-
42
- //if allowCache is true, results will be stored/returned from here
43
- function _getCachedResults() {
44
- var _cachedResults = getGlobal<{ [key: string]: IJsonSyncResult<any>; }>("utils_restmodule_cachedResults");
45
- return _cachedResults;
46
- }
47
-
48
- //cannot use from top window, example: DVP, if you open view item popup and close it too fast, there might be a pending request that never resolves since the handler code was unloaded from the window.
49
- function _getPendingRequests() {
50
- var _pendingRequests = getGlobal<IDictionary<IPendingRequest<any>>>("utils_restmodule_pendingRequests", undefined, true);
51
- return _pendingRequests;
52
- }
53
-
54
-
55
- function getDefaultOptions(): IRestOptions {
56
- return {
57
- includeDigestInPost: true,
58
- headers: {
59
- }
60
- };
61
- }
62
-
63
- function fillHeaders(xhr: XMLHttpRequest, headers: { [key: string]: string; }) {
64
- for (let header in headers)
65
- if (hasOwnProperty(headers, header)) {
66
- let val = headers[header];
67
- if (!isNullOrEmptyString(val))
68
- xhr.setRequestHeader(header, val);
69
- }
70
- }
71
-
72
- function getXhr(url: string, body?: IRequestBody, options?: IRestOptions, async = true): IRequestObjects {
73
- var optionsWithDefaults = assign<IRestOptions>({}, getDefaultOptions(), options);
74
- let myCacheOptions: IRestCacheOptions & { cacheKey?: string; } = {};
75
- Object.keys(AllRestCacheOptionsKeys).forEach(key => {
76
- if (hasOwnProperty(optionsWithDefaults, key)) {
77
- myCacheOptions[key] = optionsWithDefaults[key];
78
- delete optionsWithDefaults[key];
79
- }
80
- });
81
- let myOptions: IRestRequestOptions = { ...optionsWithDefaults };
82
-
83
- var xhr: XMLHttpRequest = new XMLHttpRequest();
84
-
85
- let jsonType = myOptions.jsonMetadata || jsonTypes.verbose;
86
-
87
- if (myOptions.cors) {
88
- xhr.withCredentials = true;
89
- }
90
-
91
- if (isNullOrUndefined(myOptions.headers)) myOptions.headers = {};//issue 660 in case the sender sent headers as null
92
- if (isNullOrUndefined(myOptions.headers["Accept"])) {
93
- myOptions.headers["Accept"] = jsonType;
94
- }
95
-
96
- let method = myOptions.method;
97
- if (isNullOrEmptyString(method)) {
98
- method = isNullOrUndefined(body) ? "GET" : "POST";
99
- }
100
-
101
- myOptions.method = method;
102
- xhr.open(method, url, async !== false);
103
- if (method === "GET") {
104
- if (myOptions.includeDigestInGet === true) {//by default don't add it, unless explicitly asked in options
105
- xhr.setRequestHeader("X-RequestDigest", getFormDigest(myOptions.spWebUrl));
106
- }
107
- }
108
- else if (method === "POST") {
109
- if (isNullOrUndefined(myOptions.headers["content-type"])) {
110
- myOptions.headers["content-type"] = jsonType;
111
- }
112
-
113
- if (myOptions.includeDigestInPost !== false) {//if explicitly set to false - don't include it
114
- xhr.setRequestHeader("X-RequestDigest", getFormDigest(myOptions.spWebUrl));
115
- }
116
- }
117
-
118
- if (!isNullOrEmptyString(myOptions.xHttpMethod)) {
119
- myOptions.headers["X-HTTP-Method"] = myOptions.xHttpMethod;
120
-
121
- if (myOptions.xHttpMethod === "MERGE" || myOptions.xHttpMethod === "DELETE" || myOptions.xHttpMethod === "PUT") {
122
- myOptions.headers["If-Match"] = "*";// update regadless of other user changes
123
- }
124
- }
125
-
126
- fillHeaders(xhr, myOptions.headers);
127
-
128
- if (!isNullOrEmptyString(myOptions.responseType) && myOptions.responseType !== "text") {
129
- if (myCacheOptions.allowCache === true &&
130
- (myOptions.responseType === "blob" || myOptions.responseType === "arraybuffer" || myOptions.responseType === "document")) {
131
- logger.warn("When allowCache is true, Blob, ArrayBuffer and Document response types will only be stored in runtime memory and not committed to local storage.");
132
- }
133
- xhr.responseType = myOptions.responseType;
134
- }
135
-
136
- //we do not support cache if there is a request body
137
- //postCacheKey - allow cache on post request for stuff like get item by CamlQuery
138
- if (isNullOrUndefined(body) || !isNullOrEmptyString(myOptions.postCacheKey)) {
139
- myCacheOptions.cacheKey = (url + '|' + JSON.stringify(myOptions)).trim().toLowerCase();
140
- }
141
-
142
- return {
143
- xhr: xhr,
144
- options: myOptions,
145
- cacheOptions: myCacheOptions
146
- };
147
- }
148
-
149
- function getCachedResult<T>(objects: IRequestObjects): IJsonSyncResult<T> {
150
- var cacheKey = objects.cacheOptions.cacheKey;
151
- if (objects.cacheOptions.allowCache === true && objects.cacheOptions.forceCacheUpdate !== true) {
152
- if (isNullOrEmptyString(cacheKey)) {
153
- //logger.warn('cache is not supported for this type of request.');
154
- return null;
155
- }
156
-
157
- let _cachedResults = _getCachedResults();
158
- if (isNullOrUndefined(_cachedResults[cacheKey])) {
159
- //try to load from local storage
160
- let result = getCacheItem<IJsonSyncResult<T>>('jsr_' + cacheKey);
161
-
162
- if (!isNullOrUndefined(result) && (result.success === true || result.status === 404)) {
163
- if (!result.cachedTime) {
164
- let now = new Date();
165
- now.setDate(-1);
166
- result.cachedTime = now.getTime();
167
- }
168
-
169
- _cachedResults[cacheKey] = result;
170
- }
171
- }
172
-
173
- if (!isNullOrUndefined(_cachedResults[cacheKey])) {
174
- let result = _cachedResults[cacheKey];
175
-
176
- var maxAge = isNumber(objects.cacheOptions.maxAge) && objects.cacheOptions.maxAge > 0 ? objects.cacheOptions.maxAge : null;
177
-
178
- if (maxAge && result.cachedTime) {
179
- let now = new Date().getTime();
180
- var cachedTime = result.cachedTime;
181
- var validUntil = cachedTime + (maxAge * 1000);
182
-
183
- if (now > validUntil) {
184
- logger.debug("getCachedResult - entry has out lived max age");
185
- return null;
186
- }
187
- }
188
-
189
- return {
190
- ..._cachedResults[cacheKey],
191
- result: _canSafelyStringify(_cachedResults[cacheKey].result) ?
192
- jsonClone(_cachedResults[cacheKey].result) :
193
- _cachedResults[cacheKey].result
194
- };
195
- }
196
- }
197
- return null;
198
- }
199
-
200
- function setCachedResult<T>(cacheOptions: IRestCacheOptions & { cacheKey?: string; }, response: IJsonSyncResult<T>) {
201
- if (isNullOrEmptyString(cacheOptions.cacheKey)) {
202
- return;
203
- }
204
- response.cachedTime = new Date().getTime();
205
- let isResultSerializable = _canSafelyStringify(response.result);
206
-
207
- let _cachedResults = _getCachedResults();
208
- _cachedResults[cacheOptions.cacheKey] = {
209
- ...response,
210
- result: isResultSerializable ? jsonClone(response.result) : response.result
211
- };
212
-
213
- if (!isResultSerializable) {
214
- logger.warn("When allowCache is true, Blob, ArrayBuffer and Document response types will only be stored in runtime memory and not committed to local storage.");
215
- }
216
-
217
- if (isResultSerializable && !isNullOrUndefined(cacheOptions.localStorageExpiration) && response && response.success === true) {
218
- setCacheItem('jsr_' + cacheOptions.cacheKey, response, cacheOptions.localStorageExpiration as any);
219
- }
220
- }
221
-
222
- function getPendingRequest<T = any>(objects: IRequestObjects): IPendingRequest<T> {
223
- var cacheKey = objects.cacheOptions.cacheKey;
224
- // if (isNullOrEmptyString(cacheKey)) {
225
- // logger.warn('cache is not supported for this type of request.');
226
- // }
227
-
228
- let _pendingRequests = _getPendingRequests();
229
-
230
- if (!isNullOrEmptyString(cacheKey) && !isNullOrUndefined(_pendingRequests[cacheKey])) {
231
- //returned from cache
232
- return _pendingRequests[cacheKey];
233
- }
234
- return null;
235
- }
236
-
237
- function getParsedResponse<T>(objects: IRequestObjects) {
238
- let parsedResponse: T = null;
239
- if (!isNullOrEmptyString(objects.options.responseType) && objects.options.responseType !== "text") {
240
- parsedResponse = objects.xhr.response;
241
- } else {
242
- if (objects.options.responseType !== "text") {
243
- //Only try to parse if caller didn't expect text explicitly
244
- parsedResponse = jsonParse(objects.xhr.responseText);
245
- }
246
- if (isNullOrUndefined(parsedResponse)) {
247
- parsedResponse = objects.xhr.responseText as any;
248
- }
249
- }
250
-
251
- return parsedResponse;
252
- }
253
-
254
- function setPendingRequest<T = any>(cacheKey: string, objects: IRequestObjects, promise: Promise<T>): IPendingRequest<T> {
255
- if (isNullOrEmptyString(cacheKey)) {
256
- return null;
257
- }
258
-
259
- let _pendingRequests = _getPendingRequests();
260
- _pendingRequests[cacheKey] = { objects: objects, promise: promise, listeners: [] };
261
- return _pendingRequests[cacheKey];
262
- }
263
-
264
- function removePendingRequest(cacheKey: string) {
265
- if (isNullOrEmptyString(cacheKey)) {
266
- return;
267
- }
268
-
269
- try {
270
- let _pendingRequests = _getPendingRequests();
271
- _pendingRequests[cacheKey] = null;
272
- delete _pendingRequests[cacheKey];
273
- } catch (ex) {
274
- }
275
- }
276
-
277
- function _getRestErrorMessage(xhr: XMLHttpRequest) {
278
- try {
279
- //issue 245, external datasource might return error.code as a number with a plain text message.
280
- if (!isNullOrUndefined(xhr) && !isNullOrEmptyString(xhr.responseText)) {
281
- let error = jsonParse<{ code: string | number; message: string; }>(xhr.responseText);
282
- if (!isNullOrUndefined(error) && !isNullOrEmptyString(error.code)) {
283
- if (isString(error.code) && error.code.indexOf("SPQueryThrottledException") !== -1) {
284
- return !isNullOrEmptyString(error.message) ? `${error.message} (SPQueryThrottledException)` : `an error occured (SPQueryThrottledException)`;
285
- }
286
- if (!isNullOrEmptyString(error.message)) return error.message;
287
- }
288
- }
289
- } catch (e) { }
290
- return `an error occured`;
291
- }
292
-
293
- function _canSafelyStringify(result: any) {
294
- //this would return false positives on some response strings
295
- if (isPrimitiveValue(result)) {
296
- return true;
297
- } else if (isObject(result)) {
298
- if (
299
- ("ArrayBuffer" in globalThis && (result instanceof ArrayBuffer))
300
- || ("Blob" in globalThis && (result instanceof Blob))
301
- || ("Document" in globalThis && (result instanceof Document))
302
- ) {
303
- return false;
304
- }
305
- return true;
306
- } else {
307
- return false;//shouldn't get here... since result should either be primitive value or an object
308
- }
309
- }
310
-
311
- export function GetJsonSync<T>(url: string, body?: IRequestBody, options?: IRestOptions): IJsonSyncResult<T> {
312
- let xhr: XMLHttpRequest = null;
313
- let syncResult: IJsonSyncResult<T> = null;
314
- let objects = getXhr(url, body, options, false);
315
- try {
316
- var cachedResult = getCachedResult<T>(objects);
317
- if (!isNullOrUndefined(cachedResult)) {
318
- return cachedResult;
319
- }
320
-
321
- xhr = objects.xhr;
322
-
323
- if (objects.options.method === "GET") {
324
- objects.xhr.send();
325
- }
326
- else {
327
- objects.xhr.send(body);
328
- }
329
-
330
- if (objects.options.returnXhrObject === true) {
331
- return {
332
- status: xhr.status,
333
- success: xhr.status >= 200 && xhr.status < 400,
334
- result: xhr as any as T
335
- } as any;
336
- }
337
-
338
- // status < 300 leaves out 304 responses which are successful responses so we should use < 400
339
- if (objects.xhr.status >= 200 && objects.xhr.status < 400) {
340
- let result: T = getParsedResponse<T>(objects);
341
-
342
- syncResult = {
343
- status: xhr.status,
344
- success: true,
345
- result: result
346
- };
347
-
348
- setCachedResult(objects.cacheOptions, syncResult);
349
- } else {
350
- throw new Error("Error code: " + objects.xhr.status);
351
- }
352
- } catch (e) {
353
- //make sure errors get here and not returned without catch...
354
- let responseText = xhr.responseText;
355
- let errorData: any;
356
- if (!isNullOrEmptyString(responseText)) {
357
- errorData = jsonParse(responseText);
358
- if (isNullOrUndefined(errorData)) {
359
- errorData = responseText;
360
- }
361
- }
362
-
363
- let errorMessage = _getRestErrorMessage(xhr);
364
-
365
- syncResult = {
366
- status: xhr && xhr.status || -1,
367
- success: false,
368
- errorData: errorData,
369
- errorMessage: errorMessage
370
- };
371
- setCachedResult(objects.cacheOptions, syncResult);
372
- }
373
- return syncResult;
374
- }
375
-
376
- export function GetJson<T>(url: string, body?: IRequestBody, options?: IRestOptions): Promise<T> {
377
- try {
378
- let objects = getXhr(url, body, options);
379
-
380
- var cachedResult = getCachedResult<T>(objects);
381
- if (!isNullOrUndefined(cachedResult)) {
382
- if (!supressDebugMessages) {
383
- logger.debug(`GetJson - request fulfilled by cached results: ${url}`);
384
- }
385
- if (cachedResult.success) {
386
- return Promise.resolve(cachedResult.result);
387
- }
388
- else {
389
- return Promise.reject({
390
- message: isNullOrEmptyString(cachedResult.errorMessage) ? "an error occured in cached results" : cachedResult.errorMessage,
391
- errorData: cachedResult.errorData
392
- } as IRestError);
393
- }
394
- }
395
-
396
- var pendingRequest = getPendingRequest(objects);
397
- var xhrPromise: Promise<T> = null;
398
-
399
- if (isNullOrUndefined(pendingRequest)) {
400
- if (!supressDebugMessages) {
401
- logger.debug(`GetJson - request fulfilled by new request: ${url}`);
402
- }
403
- xhrPromise = new Promise((resolve, reject) => {
404
- let promiseResolved = false;
405
-
406
- objects.xhr.addEventListener("readystatechange", () => {
407
- if (objects.xhr.readyState === XMLHttpRequest.DONE) {
408
- try {
409
- if (!supressDebugMessages) {
410
- logger.debug(`readystate changed: ${url}`);
411
- }
412
- if (objects.options.returnXhrObject === true) {
413
- promiseResolved = true;
414
- resolve(objects.xhr as any as T);
415
- }
416
-
417
- let parsedResponse: T = getParsedResponse<T>(objects);
418
-
419
- // status < 300 leaves out 304 responses which are successful responses so we should use < 400
420
- if (objects.xhr.status >= 200 && objects.xhr.status < 400) {
421
- setCachedResult(objects.cacheOptions, { status: objects.xhr.status, success: true, result: parsedResponse });
422
- promiseResolved = true;
423
- resolve(parsedResponse);
424
- if (pendingRequest) {
425
- pendingRequest.listeners.forEach(l => {
426
- let listenerParsedResponse: T = getParsedResponse<T>(objects);
427
- l.resolve(listenerParsedResponse);
428
- });
429
- }
430
- } else {
431
- let errorMessage = _getRestErrorMessage(objects.xhr);
432
- setCachedResult(objects.cacheOptions, { status: objects.xhr.status, success: false, errorData: parsedResponse, errorMessage: errorMessage });
433
- promiseResolved = true;
434
- reject({ message: errorMessage, errorData: parsedResponse, xhr: objects.xhr } as IRestError);
435
- if (pendingRequest) {
436
- pendingRequest.listeners.forEach(l => l.reject({ message: errorMessage, errorData: parsedResponse, xhr: objects.xhr } as IRestError));
437
- }
438
- }
439
- } catch (e) {
440
- if (!supressDebugMessages) {
441
- logger.error(`readystate error: ${e}: ${url}`);
442
- }
443
- }
444
- if (!promiseResolved) {
445
- if (!supressDebugMessages) {
446
- logger.debug(`promise NOT resolved. resoving myself...: ${url}`);
447
- }
448
-
449
- promiseResolved = true;
450
- reject({ message: "an unknown error occured", xhr: objects.xhr } as IRestError);
451
- }
452
- else if (!supressDebugMessages) {
453
- logger.debug(`promise resolved. removing pending request object: ${url}`);
454
- }
455
-
456
- removePendingRequest(objects.cacheOptions.cacheKey);
457
- }
458
- });
459
- });
460
-
461
- if (objects.xhr.readyState === XMLHttpRequest.OPENED) {
462
- //only set this if our request is on the way
463
- pendingRequest = setPendingRequest(objects.cacheOptions.cacheKey, objects, xhrPromise);
464
-
465
- if (!supressDebugMessages) {
466
- logger.debug(`${url}: sending request, setPendingRequest`);
467
- }
468
-
469
- if (objects.options.method === "GET") {
470
- objects.xhr.send();
471
- } else {
472
- objects.xhr.send(body);
473
- }
474
- }
475
- else logger.error('xhr not opened');
476
- } else if (pendingRequest) {
477
- if (!supressDebugMessages) {
478
- logger.debug(`GetJson - request fulfilled by pending requests: ${url}`);
479
- }
480
- //must add a separate promise, so that I can make a full(not shallow) copy of the result.
481
- //this way if the first caller changes the object, the second caller gets it unchanged.
482
- xhrPromise = new Promise<T>((resolve, reject) => {
483
- pendingRequest.listeners.push({
484
- resolve: (result: T) => resolve(result),
485
- reject: (reason) => reject(reason)
486
- });
487
- });
488
- }
489
-
490
- return xhrPromise;
491
- } catch (e) {
492
- return Promise.reject({ message: "an error occured" });
493
- }
494
- }
495
-
496
- /** if you detected a change that invalidates all requests stored in memory - this will clear all in-memory cached results */
497
- export function GetJsonClearCache() {
498
- let _cachedResults = _getCachedResults();
499
- Object.keys(_cachedResults).forEach(key => {
500
- delete _cachedResults[key];
501
- });
1
+ import { jsonParse } from "../helpers/json";
2
+ import { assign, getGlobal, hasOwnProperty, jsonClone } from "../helpers/objects";
3
+ import { isNullOrEmptyString, isNullOrUndefined, isNumber, isObject, isPrimitiveValue, isString } from "../helpers/typecheckers";
4
+ import { IDictionary } from "../types/common.types";
5
+ import { AllRestCacheOptionsKeys, IJsonSyncResult, IRequestBody, IRequestObjects, IRestCacheOptions, IRestError, IRestOptions, IRestRequestOptions, jsonTypes } from "../types/rest.types";
6
+ import { ConsoleLogger } from "./consolelogger";
7
+ import { getCacheItem, setCacheItem } from "./localstoragecache";
8
+ import { getFormDigest } from "./sharepoint.rest/web";
9
+
10
+ var logger = ConsoleLogger.get("kwizcom.rest.module");
11
+ const supressDebugMessages = true;
12
+
13
+ /** cache for 1 day */
14
+ export const noLocalCache: IRestOptions = { allowCache: false };
15
+ /** cache for 1 days */
16
+ export const longLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 1 } };
17
+ /** cache for 2 days */
18
+ export const extraLongLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 2 } };
19
+ /** cache for 7 days */
20
+ export const weeekLongLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 7 } };
21
+ /** cache for 30 days */
22
+ export const monthLongLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { days: 30 } };
23
+ /** cache for 5 minutes */
24
+ export const shortLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { minutes: 5 } };
25
+ /** cache for 15 minutes */
26
+ export const mediumLocalCache: IRestOptions = { allowCache: true, localStorageExpiration: { minutes: 15 } };
27
+
28
+ interface IPendingRequest<T> {
29
+ objects: IRequestObjects;
30
+ /**the main promise */
31
+ promise: Promise<T>;
32
+ /**additional listeners
33
+ * must add a separate promise, so that I can make a full(not shallow) copy of the result.
34
+ * this way if the first caller changes the object, the second caller gets it unchanged.
35
+ */
36
+ listeners: {
37
+ resolve: (result: T) => void;
38
+ reject: (reason: any) => void;
39
+ }[];
40
+ }
41
+
42
+ //if allowCache is true, results will be stored/returned from here
43
+ function _getCachedResults() {
44
+ var _cachedResults = getGlobal<{ [key: string]: IJsonSyncResult<any>; }>("utils_restmodule_cachedResults");
45
+ return _cachedResults;
46
+ }
47
+
48
+ //cannot use from top window, example: DVP, if you open view item popup and close it too fast, there might be a pending request that never resolves since the handler code was unloaded from the window.
49
+ function _getPendingRequests() {
50
+ var _pendingRequests = getGlobal<IDictionary<IPendingRequest<any>>>("utils_restmodule_pendingRequests", undefined, true);
51
+ return _pendingRequests;
52
+ }
53
+
54
+
55
+ function getDefaultOptions(): IRestOptions {
56
+ return {
57
+ includeDigestInPost: true,
58
+ headers: {
59
+ }
60
+ };
61
+ }
62
+
63
+ function fillHeaders(xhr: XMLHttpRequest, headers: { [key: string]: string; }) {
64
+ for (let header in headers)
65
+ if (hasOwnProperty(headers, header)) {
66
+ let val = headers[header];
67
+ if (!isNullOrEmptyString(val))
68
+ xhr.setRequestHeader(header, val);
69
+ }
70
+ }
71
+
72
+ function getXhr(url: string, body?: IRequestBody, options?: IRestOptions, async = true): IRequestObjects {
73
+ var optionsWithDefaults = assign<IRestOptions>({}, getDefaultOptions(), options);
74
+ let myCacheOptions: IRestCacheOptions & { cacheKey?: string; } = {};
75
+ Object.keys(AllRestCacheOptionsKeys).forEach(key => {
76
+ if (hasOwnProperty(optionsWithDefaults, key)) {
77
+ myCacheOptions[key] = optionsWithDefaults[key];
78
+ delete optionsWithDefaults[key];
79
+ }
80
+ });
81
+ let myOptions: IRestRequestOptions = { ...optionsWithDefaults };
82
+
83
+ var xhr: XMLHttpRequest = new XMLHttpRequest();
84
+
85
+ let jsonType = myOptions.jsonMetadata || jsonTypes.verbose;
86
+
87
+ if (myOptions.cors) {
88
+ xhr.withCredentials = true;
89
+ }
90
+
91
+ if (isNullOrUndefined(myOptions.headers)) myOptions.headers = {};//issue 660 in case the sender sent headers as null
92
+ if (isNullOrUndefined(myOptions.headers["Accept"])) {
93
+ myOptions.headers["Accept"] = jsonType;
94
+ }
95
+
96
+ let method = myOptions.method;
97
+ if (isNullOrEmptyString(method)) {
98
+ method = isNullOrUndefined(body) ? "GET" : "POST";
99
+ }
100
+
101
+ myOptions.method = method;
102
+ xhr.open(method, url, async !== false);
103
+ if (method === "GET") {
104
+ if (myOptions.includeDigestInGet === true) {//by default don't add it, unless explicitly asked in options
105
+ xhr.setRequestHeader("X-RequestDigest", getFormDigest(myOptions.spWebUrl));
106
+ }
107
+ }
108
+ else if (method === "POST") {
109
+ if (isNullOrUndefined(myOptions.headers["content-type"])) {
110
+ myOptions.headers["content-type"] = jsonType;
111
+ }
112
+
113
+ if (myOptions.includeDigestInPost !== false) {//if explicitly set to false - don't include it
114
+ xhr.setRequestHeader("X-RequestDigest", getFormDigest(myOptions.spWebUrl));
115
+ }
116
+ }
117
+
118
+ if (!isNullOrEmptyString(myOptions.xHttpMethod)) {
119
+ myOptions.headers["X-HTTP-Method"] = myOptions.xHttpMethod;
120
+
121
+ if (myOptions.xHttpMethod === "MERGE" || myOptions.xHttpMethod === "DELETE" || myOptions.xHttpMethod === "PUT") {
122
+ myOptions.headers["If-Match"] = "*";// update regadless of other user changes
123
+ }
124
+ }
125
+
126
+ fillHeaders(xhr, myOptions.headers);
127
+
128
+ if (!isNullOrEmptyString(myOptions.responseType) && myOptions.responseType !== "text") {
129
+ if (myCacheOptions.allowCache === true &&
130
+ (myOptions.responseType === "blob" || myOptions.responseType === "arraybuffer" || myOptions.responseType === "document")) {
131
+ logger.warn("When allowCache is true, Blob, ArrayBuffer and Document response types will only be stored in runtime memory and not committed to local storage.");
132
+ }
133
+ xhr.responseType = myOptions.responseType;
134
+ }
135
+
136
+ //we do not support cache if there is a request body
137
+ //postCacheKey - allow cache on post request for stuff like get item by CamlQuery
138
+ if (isNullOrUndefined(body) || !isNullOrEmptyString(myOptions.postCacheKey)) {
139
+ myCacheOptions.cacheKey = (url + '|' + JSON.stringify(myOptions)).trim().toLowerCase();
140
+ }
141
+
142
+ return {
143
+ xhr: xhr,
144
+ options: myOptions,
145
+ cacheOptions: myCacheOptions
146
+ };
147
+ }
148
+
149
+ function getCachedResult<T>(objects: IRequestObjects): IJsonSyncResult<T> {
150
+ var cacheKey = objects.cacheOptions.cacheKey;
151
+ if (objects.cacheOptions.allowCache === true && objects.cacheOptions.forceCacheUpdate !== true) {
152
+ if (isNullOrEmptyString(cacheKey)) {
153
+ //logger.warn('cache is not supported for this type of request.');
154
+ return null;
155
+ }
156
+
157
+ let _cachedResults = _getCachedResults();
158
+ if (isNullOrUndefined(_cachedResults[cacheKey])) {
159
+ //try to load from local storage
160
+ let result = getCacheItem<IJsonSyncResult<T>>('jsr_' + cacheKey);
161
+
162
+ if (!isNullOrUndefined(result) && (result.success === true || result.status === 404)) {
163
+ if (!result.cachedTime) {
164
+ let now = new Date();
165
+ now.setDate(-1);
166
+ result.cachedTime = now.getTime();
167
+ }
168
+
169
+ _cachedResults[cacheKey] = result;
170
+ }
171
+ }
172
+
173
+ if (!isNullOrUndefined(_cachedResults[cacheKey])) {
174
+ let result = _cachedResults[cacheKey];
175
+
176
+ var maxAge = isNumber(objects.cacheOptions.maxAge) && objects.cacheOptions.maxAge > 0 ? objects.cacheOptions.maxAge : null;
177
+
178
+ if (maxAge && result.cachedTime) {
179
+ let now = new Date().getTime();
180
+ var cachedTime = result.cachedTime;
181
+ var validUntil = cachedTime + (maxAge * 1000);
182
+
183
+ if (now > validUntil) {
184
+ logger.debug("getCachedResult - entry has out lived max age");
185
+ return null;
186
+ }
187
+ }
188
+
189
+ return {
190
+ ..._cachedResults[cacheKey],
191
+ result: _canSafelyStringify(_cachedResults[cacheKey].result) ?
192
+ jsonClone(_cachedResults[cacheKey].result) :
193
+ _cachedResults[cacheKey].result
194
+ };
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+
200
+ function setCachedResult<T>(cacheOptions: IRestCacheOptions & { cacheKey?: string; }, response: IJsonSyncResult<T>) {
201
+ if (isNullOrEmptyString(cacheOptions.cacheKey)) {
202
+ return;
203
+ }
204
+ response.cachedTime = new Date().getTime();
205
+ let isResultSerializable = _canSafelyStringify(response.result);
206
+
207
+ let _cachedResults = _getCachedResults();
208
+ _cachedResults[cacheOptions.cacheKey] = {
209
+ ...response,
210
+ result: isResultSerializable ? jsonClone(response.result) : response.result
211
+ };
212
+
213
+ if (!isResultSerializable) {
214
+ logger.warn("When allowCache is true, Blob, ArrayBuffer and Document response types will only be stored in runtime memory and not committed to local storage.");
215
+ }
216
+
217
+ if (isResultSerializable && !isNullOrUndefined(cacheOptions.localStorageExpiration) && response && response.success === true) {
218
+ setCacheItem('jsr_' + cacheOptions.cacheKey, response, cacheOptions.localStorageExpiration as any);
219
+ }
220
+ }
221
+
222
+ function getPendingRequest<T = any>(objects: IRequestObjects): IPendingRequest<T> {
223
+ var cacheKey = objects.cacheOptions.cacheKey;
224
+ // if (isNullOrEmptyString(cacheKey)) {
225
+ // logger.warn('cache is not supported for this type of request.');
226
+ // }
227
+
228
+ let _pendingRequests = _getPendingRequests();
229
+
230
+ if (!isNullOrEmptyString(cacheKey) && !isNullOrUndefined(_pendingRequests[cacheKey])) {
231
+ //returned from cache
232
+ return _pendingRequests[cacheKey];
233
+ }
234
+ return null;
235
+ }
236
+
237
+ function getParsedResponse<T>(objects: IRequestObjects) {
238
+ let parsedResponse: T = null;
239
+ if (!isNullOrEmptyString(objects.options.responseType) && objects.options.responseType !== "text") {
240
+ parsedResponse = objects.xhr.response;
241
+ } else {
242
+ if (objects.options.responseType !== "text") {
243
+ //Only try to parse if caller didn't expect text explicitly
244
+ parsedResponse = jsonParse(objects.xhr.responseText);
245
+ }
246
+ if (isNullOrUndefined(parsedResponse)) {
247
+ parsedResponse = objects.xhr.responseText as any;
248
+ }
249
+ }
250
+
251
+ return parsedResponse;
252
+ }
253
+
254
+ function setPendingRequest<T = any>(cacheKey: string, objects: IRequestObjects, promise: Promise<T>): IPendingRequest<T> {
255
+ if (isNullOrEmptyString(cacheKey)) {
256
+ return null;
257
+ }
258
+
259
+ let _pendingRequests = _getPendingRequests();
260
+ _pendingRequests[cacheKey] = { objects: objects, promise: promise, listeners: [] };
261
+ return _pendingRequests[cacheKey];
262
+ }
263
+
264
+ function removePendingRequest(cacheKey: string) {
265
+ if (isNullOrEmptyString(cacheKey)) {
266
+ return;
267
+ }
268
+
269
+ try {
270
+ let _pendingRequests = _getPendingRequests();
271
+ _pendingRequests[cacheKey] = null;
272
+ delete _pendingRequests[cacheKey];
273
+ } catch (ex) {
274
+ }
275
+ }
276
+
277
+ function _getRestErrorMessage(xhr: XMLHttpRequest) {
278
+ try {
279
+ //issue 245, external datasource might return error.code as a number with a plain text message.
280
+ if (!isNullOrUndefined(xhr) && !isNullOrEmptyString(xhr.responseText)) {
281
+ let error = jsonParse<{ code: string | number; message: string; }>(xhr.responseText);
282
+ if (!isNullOrUndefined(error) && !isNullOrEmptyString(error.code)) {
283
+ if (isString(error.code) && error.code.indexOf("SPQueryThrottledException") !== -1) {
284
+ return !isNullOrEmptyString(error.message) ? `${error.message} (SPQueryThrottledException)` : `an error occured (SPQueryThrottledException)`;
285
+ }
286
+ if (!isNullOrEmptyString(error.message)) return error.message;
287
+ }
288
+ }
289
+ } catch (e) { }
290
+ return `an error occured`;
291
+ }
292
+
293
+ function _canSafelyStringify(result: any) {
294
+ //this would return false positives on some response strings
295
+ if (isPrimitiveValue(result)) {
296
+ return true;
297
+ } else if (isObject(result)) {
298
+ if (
299
+ ("ArrayBuffer" in globalThis && (result instanceof ArrayBuffer))
300
+ || ("Blob" in globalThis && (result instanceof Blob))
301
+ || ("Document" in globalThis && (result instanceof Document))
302
+ ) {
303
+ return false;
304
+ }
305
+ return true;
306
+ } else {
307
+ return false;//shouldn't get here... since result should either be primitive value or an object
308
+ }
309
+ }
310
+
311
+ export function GetJsonSync<T>(url: string, body?: IRequestBody, options?: IRestOptions): IJsonSyncResult<T> {
312
+ let xhr: XMLHttpRequest = null;
313
+ let syncResult: IJsonSyncResult<T> = null;
314
+ let objects = getXhr(url, body, options, false);
315
+ try {
316
+ var cachedResult = getCachedResult<T>(objects);
317
+ if (!isNullOrUndefined(cachedResult)) {
318
+ return cachedResult;
319
+ }
320
+
321
+ xhr = objects.xhr;
322
+
323
+ if (objects.options.method === "GET") {
324
+ objects.xhr.send();
325
+ }
326
+ else {
327
+ objects.xhr.send(body);
328
+ }
329
+
330
+ if (objects.options.returnXhrObject === true) {
331
+ return {
332
+ status: xhr.status,
333
+ success: xhr.status >= 200 && xhr.status < 400,
334
+ result: xhr as any as T
335
+ } as any;
336
+ }
337
+
338
+ // status < 300 leaves out 304 responses which are successful responses so we should use < 400
339
+ if (objects.xhr.status >= 200 && objects.xhr.status < 400) {
340
+ let result: T = getParsedResponse<T>(objects);
341
+
342
+ syncResult = {
343
+ status: xhr.status,
344
+ success: true,
345
+ result: result
346
+ };
347
+
348
+ setCachedResult(objects.cacheOptions, syncResult);
349
+ } else {
350
+ throw new Error("Error code: " + objects.xhr.status);
351
+ }
352
+ } catch (e) {
353
+ //make sure errors get here and not returned without catch...
354
+ let responseText = xhr.responseText;
355
+ let errorData: any;
356
+ if (!isNullOrEmptyString(responseText)) {
357
+ errorData = jsonParse(responseText);
358
+ if (isNullOrUndefined(errorData)) {
359
+ errorData = responseText;
360
+ }
361
+ }
362
+
363
+ let errorMessage = _getRestErrorMessage(xhr);
364
+
365
+ syncResult = {
366
+ status: xhr && xhr.status || -1,
367
+ success: false,
368
+ errorData: errorData,
369
+ errorMessage: errorMessage
370
+ };
371
+ setCachedResult(objects.cacheOptions, syncResult);
372
+ }
373
+ return syncResult;
374
+ }
375
+
376
+ export function GetJson<T>(url: string, body?: IRequestBody, options?: IRestOptions): Promise<T> {
377
+ try {
378
+ let objects = getXhr(url, body, options);
379
+
380
+ var cachedResult = getCachedResult<T>(objects);
381
+ if (!isNullOrUndefined(cachedResult)) {
382
+ if (!supressDebugMessages) {
383
+ logger.debug(`GetJson - request fulfilled by cached results: ${url}`);
384
+ }
385
+ if (cachedResult.success) {
386
+ return Promise.resolve(cachedResult.result);
387
+ }
388
+ else {
389
+ return Promise.reject({
390
+ message: isNullOrEmptyString(cachedResult.errorMessage) ? "an error occured in cached results" : cachedResult.errorMessage,
391
+ errorData: cachedResult.errorData
392
+ } as IRestError);
393
+ }
394
+ }
395
+
396
+ var pendingRequest = getPendingRequest(objects);
397
+ var xhrPromise: Promise<T> = null;
398
+
399
+ if (isNullOrUndefined(pendingRequest)) {
400
+ if (!supressDebugMessages) {
401
+ logger.debug(`GetJson - request fulfilled by new request: ${url}`);
402
+ }
403
+ xhrPromise = new Promise((resolve, reject) => {
404
+ let promiseResolved = false;
405
+
406
+ objects.xhr.addEventListener("readystatechange", () => {
407
+ if (objects.xhr.readyState === XMLHttpRequest.DONE) {
408
+ try {
409
+ if (!supressDebugMessages) {
410
+ logger.debug(`readystate changed: ${url}`);
411
+ }
412
+ if (objects.options.returnXhrObject === true) {
413
+ promiseResolved = true;
414
+ resolve(objects.xhr as any as T);
415
+ }
416
+
417
+ let parsedResponse: T = getParsedResponse<T>(objects);
418
+
419
+ // status < 300 leaves out 304 responses which are successful responses so we should use < 400
420
+ if (objects.xhr.status >= 200 && objects.xhr.status < 400) {
421
+ setCachedResult(objects.cacheOptions, { status: objects.xhr.status, success: true, result: parsedResponse });
422
+ promiseResolved = true;
423
+ resolve(parsedResponse);
424
+ if (pendingRequest) {
425
+ pendingRequest.listeners.forEach(l => {
426
+ let listenerParsedResponse: T = getParsedResponse<T>(objects);
427
+ l.resolve(listenerParsedResponse);
428
+ });
429
+ }
430
+ } else {
431
+ let errorMessage = _getRestErrorMessage(objects.xhr);
432
+ setCachedResult(objects.cacheOptions, { status: objects.xhr.status, success: false, errorData: parsedResponse, errorMessage: errorMessage });
433
+ promiseResolved = true;
434
+ reject({ message: errorMessage, errorData: parsedResponse, xhr: objects.xhr } as IRestError);
435
+ if (pendingRequest) {
436
+ pendingRequest.listeners.forEach(l => l.reject({ message: errorMessage, errorData: parsedResponse, xhr: objects.xhr } as IRestError));
437
+ }
438
+ }
439
+ } catch (e) {
440
+ if (!supressDebugMessages) {
441
+ logger.error(`readystate error: ${e}: ${url}`);
442
+ }
443
+ }
444
+ if (!promiseResolved) {
445
+ if (!supressDebugMessages) {
446
+ logger.debug(`promise NOT resolved. resoving myself...: ${url}`);
447
+ }
448
+
449
+ promiseResolved = true;
450
+ reject({ message: "an unknown error occured", xhr: objects.xhr } as IRestError);
451
+ }
452
+ else if (!supressDebugMessages) {
453
+ logger.debug(`promise resolved. removing pending request object: ${url}`);
454
+ }
455
+
456
+ removePendingRequest(objects.cacheOptions.cacheKey);
457
+ }
458
+ });
459
+ });
460
+
461
+ if (objects.xhr.readyState === XMLHttpRequest.OPENED) {
462
+ //only set this if our request is on the way
463
+ pendingRequest = setPendingRequest(objects.cacheOptions.cacheKey, objects, xhrPromise);
464
+
465
+ if (!supressDebugMessages) {
466
+ logger.debug(`${url}: sending request, setPendingRequest`);
467
+ }
468
+
469
+ if (objects.options.method === "GET") {
470
+ objects.xhr.send();
471
+ } else {
472
+ objects.xhr.send(body);
473
+ }
474
+ }
475
+ else logger.error('xhr not opened');
476
+ } else if (pendingRequest) {
477
+ if (!supressDebugMessages) {
478
+ logger.debug(`GetJson - request fulfilled by pending requests: ${url}`);
479
+ }
480
+ //must add a separate promise, so that I can make a full(not shallow) copy of the result.
481
+ //this way if the first caller changes the object, the second caller gets it unchanged.
482
+ xhrPromise = new Promise<T>((resolve, reject) => {
483
+ pendingRequest.listeners.push({
484
+ resolve: (result: T) => resolve(result),
485
+ reject: (reason) => reject(reason)
486
+ });
487
+ });
488
+ }
489
+
490
+ return xhrPromise;
491
+ } catch (e) {
492
+ return Promise.reject({ message: "an error occured" });
493
+ }
494
+ }
495
+
496
+ /** if you detected a change that invalidates all requests stored in memory - this will clear all in-memory cached results */
497
+ export function GetJsonClearCache() {
498
+ let _cachedResults = _getCachedResults();
499
+ Object.keys(_cachedResults).forEach(key => {
500
+ delete _cachedResults[key];
501
+ });
502
502
  }