@fleetbase/ember-core 0.0.1

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 (224) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +32 -0
  3. package/addon/adapters/application.js +155 -0
  4. package/addon/adapters/user.js +19 -0
  5. package/addon/authenticators/fleetbase.js +72 -0
  6. package/addon/decorators/fetch-from.js +55 -0
  7. package/addon/decorators/from-store.js +56 -0
  8. package/addon/decorators/is-equal.js +15 -0
  9. package/addon/exports/host-services.js +18 -0
  10. package/addon/exports/services.js +18 -0
  11. package/addon/initializers/local-storage-adapter.js +21 -0
  12. package/addon/serializers/application.js +87 -0
  13. package/addon/services/app-cache.js +59 -0
  14. package/addon/services/crud.js +226 -0
  15. package/addon/services/current-user.js +259 -0
  16. package/addon/services/fetch.js +648 -0
  17. package/addon/services/filters.js +183 -0
  18. package/addon/services/loader.js +136 -0
  19. package/addon/services/notifications.js +32 -0
  20. package/addon/services/session.js +221 -0
  21. package/addon/services/theme.js +200 -0
  22. package/addon/services/url-search-params.js +92 -0
  23. package/addon/transforms/array.js +29 -0
  24. package/addon/transforms/object.js +11 -0
  25. package/addon/transforms/raw.js +11 -0
  26. package/addon/utils/api-url.js +11 -0
  27. package/addon/utils/apply-column-filters.js +3 -0
  28. package/addon/utils/auto-serialize.js +100 -0
  29. package/addon/utils/calculate-percentage.js +3 -0
  30. package/addon/utils/close-sidebar.js +7 -0
  31. package/addon/utils/console-url.js +56 -0
  32. package/addon/utils/copy-to-clipboard.js +35 -0
  33. package/addon/utils/corslite.js +95 -0
  34. package/addon/utils/download.js +157 -0
  35. package/addon/utils/env.js +3 -0
  36. package/addon/utils/extract-coordinates.js +35 -0
  37. package/addon/utils/extract-latitude.js +28 -0
  38. package/addon/utils/extract-longitude.js +28 -0
  39. package/addon/utils/find-closest-waypoint.js +20 -0
  40. package/addon/utils/first.js +10 -0
  41. package/addon/utils/frontend-url.js +30 -0
  42. package/addon/utils/generate-slug.js +11 -0
  43. package/addon/utils/generate-uuid.js +3 -0
  44. package/addon/utils/get-length-units.js +44 -0
  45. package/addon/utils/get-meta-field-types.js +3 -0
  46. package/addon/utils/get-mime-type.js +25 -0
  47. package/addon/utils/get-model-name.js +45 -0
  48. package/addon/utils/get-permission-action.js +47 -0
  49. package/addon/utils/get-permission-resource.js +16 -0
  50. package/addon/utils/get-pod-methods.js +7 -0
  51. package/addon/utils/get-routing-host.js +44 -0
  52. package/addon/utils/get-service-name.js +21 -0
  53. package/addon/utils/get-user-options.js +21 -0
  54. package/addon/utils/get-weight-units.js +32 -0
  55. package/addon/utils/get-with-default.js +6 -0
  56. package/addon/utils/group-api-events.js +20 -0
  57. package/addon/utils/group-by.js +26 -0
  58. package/addon/utils/has-extension.js +11 -0
  59. package/addon/utils/has-json-structure.js +10 -0
  60. package/addon/utils/hason-structure.js +3 -0
  61. package/addon/utils/haversine.js +59 -0
  62. package/addon/utils/humanize.js +16 -0
  63. package/addon/utils/is-electron.js +18 -0
  64. package/addon/utils/is-email.js +5 -0
  65. package/addon/utils/is-function.js +3 -0
  66. package/addon/utils/is-image-file.js +3 -0
  67. package/addon/utils/is-iterable.js +7 -0
  68. package/addon/utils/is-json.js +9 -0
  69. package/addon/utils/is-latitude.js +3 -0
  70. package/addon/utils/is-letter.js +3 -0
  71. package/addon/utils/is-longitude.js +3 -0
  72. package/addon/utils/is-model.js +6 -0
  73. package/addon/utils/is-not-empty.js +5 -0
  74. package/addon/utils/is-not-model.js +5 -0
  75. package/addon/utils/is-numeric.js +3 -0
  76. package/addon/utils/is-object.js +3 -0
  77. package/addon/utils/is-proxy.js +5 -0
  78. package/addon/utils/is-relation-missing.js +21 -0
  79. package/addon/utils/is-valid-coordinates.js +38 -0
  80. package/addon/utils/is-video-file.js +3 -0
  81. package/addon/utils/is-waypoint-record.js +5 -0
  82. package/addon/utils/ison.js +3 -0
  83. package/addon/utils/isset.js +10 -0
  84. package/addon/utils/last.js +23 -0
  85. package/addon/utils/lazy-load-script.js +25 -0
  86. package/addon/utils/leaflet-icon.js +13 -0
  87. package/addon/utils/leaflet-points-from-coordinates.js +3 -0
  88. package/addon/utils/load-engines.js +37 -0
  89. package/addon/utils/load-extensions.js +8 -0
  90. package/addon/utils/macros/group-by.js +28 -0
  91. package/addon/utils/make-dataset.js +57 -0
  92. package/addon/utils/map-engines.js +30 -0
  93. package/addon/utils/mock-response.js +15 -0
  94. package/addon/utils/numbers-only.js +11 -0
  95. package/addon/utils/past-tense.js +40 -0
  96. package/addon/utils/path-to-route.js +10 -0
  97. package/addon/utils/polyline.js +161 -0
  98. package/addon/utils/range.js +19 -0
  99. package/addon/utils/refresh-route.js +3 -0
  100. package/addon/utils/replace-table-row.js +17 -0
  101. package/addon/utils/reverse-point.js +3 -0
  102. package/addon/utils/serialize/normalize-polymorphic-type-within-hash.js +27 -0
  103. package/addon/utils/serialize/normalize-polymorphic-type.js +13 -0
  104. package/addon/utils/serialize/normalize-relations-with-hash.js +32 -0
  105. package/addon/utils/set-column-filter-options.js +3 -0
  106. package/addon/utils/strip-html.js +3 -0
  107. package/addon/utils/to-leaflet-bounds.js +6 -0
  108. package/addon/utils/to-model.js +18 -0
  109. package/addon/utils/waypoint-label.js +3 -0
  110. package/addon/utils/with-default-value.js +5 -0
  111. package/addon/utils/words.js +5 -0
  112. package/app/adapters/user.js +1 -0
  113. package/app/authenticators/fleetbase.js +1 -0
  114. package/app/decorators/fetch-from.js +1 -0
  115. package/app/decorators/from-store.js +1 -0
  116. package/app/decorators/is-equal.js +1 -0
  117. package/app/exports/host-services.js +1 -0
  118. package/app/exports/services.js +1 -0
  119. package/app/initializers/local-storage-adapter.js +1 -0
  120. package/app/serializers/application.js +1 -0
  121. package/app/services/app-cache.js +1 -0
  122. package/app/services/crud.js +1 -0
  123. package/app/services/current-user.js +1 -0
  124. package/app/services/fetch.js +1 -0
  125. package/app/services/filters.js +1 -0
  126. package/app/services/loader.js +1 -0
  127. package/app/services/notifications.js +1 -0
  128. package/app/services/session.js +1 -0
  129. package/app/services/theme.js +1 -0
  130. package/app/services/url-search-params.js +1 -0
  131. package/app/storages/local-cache.js +12 -0
  132. package/app/storages/user-options.js +12 -0
  133. package/app/transforms/array.js +1 -0
  134. package/app/transforms/object.js +1 -0
  135. package/app/transforms/raw.js +1 -0
  136. package/app/utils/api-url.js +1 -0
  137. package/app/utils/apply-column-filters.js +1 -0
  138. package/app/utils/auto-serialize.js +1 -0
  139. package/app/utils/calculate-percentage.js +1 -0
  140. package/app/utils/close-sidebar.js +1 -0
  141. package/app/utils/console-url.js +1 -0
  142. package/app/utils/copy-to-clipboard.js +1 -0
  143. package/app/utils/corslite.js +1 -0
  144. package/app/utils/download.js +1 -0
  145. package/app/utils/env.js +1 -0
  146. package/app/utils/extract-coordinates.js +1 -0
  147. package/app/utils/extract-latitude.js +1 -0
  148. package/app/utils/extract-longitude.js +1 -0
  149. package/app/utils/find-closest-waypoint.js +1 -0
  150. package/app/utils/first.js +1 -0
  151. package/app/utils/frontend-url.js +1 -0
  152. package/app/utils/generate-slug.js +1 -0
  153. package/app/utils/generate-uuid.js +1 -0
  154. package/app/utils/get-length-units.js +1 -0
  155. package/app/utils/get-meta-field-types.js +1 -0
  156. package/app/utils/get-mime-type.js +1 -0
  157. package/app/utils/get-model-name.js +1 -0
  158. package/app/utils/get-permission-action.js +1 -0
  159. package/app/utils/get-permission-resource.js +1 -0
  160. package/app/utils/get-pod-methods.js +1 -0
  161. package/app/utils/get-routing-host.js +1 -0
  162. package/app/utils/get-service-name.js +1 -0
  163. package/app/utils/get-user-options.js +1 -0
  164. package/app/utils/get-weight-units.js +1 -0
  165. package/app/utils/get-with-default.js +1 -0
  166. package/app/utils/group-api-events.js +1 -0
  167. package/app/utils/group-by.js +1 -0
  168. package/app/utils/has-extension.js +1 -0
  169. package/app/utils/has-json-structure.js +1 -0
  170. package/app/utils/hason-structure.js +1 -0
  171. package/app/utils/haversine.js +1 -0
  172. package/app/utils/humanize.js +1 -0
  173. package/app/utils/is-electron.js +1 -0
  174. package/app/utils/is-email.js +1 -0
  175. package/app/utils/is-function.js +1 -0
  176. package/app/utils/is-image-file.js +1 -0
  177. package/app/utils/is-iterable.js +1 -0
  178. package/app/utils/is-latitude.js +1 -0
  179. package/app/utils/is-letter.js +1 -0
  180. package/app/utils/is-longitude.js +1 -0
  181. package/app/utils/is-model.js +1 -0
  182. package/app/utils/is-not-empty.js +1 -0
  183. package/app/utils/is-not-model.js +1 -0
  184. package/app/utils/is-numeric.js +1 -0
  185. package/app/utils/is-object.js +1 -0
  186. package/app/utils/is-proxy.js +1 -0
  187. package/app/utils/is-relation-missing.js +1 -0
  188. package/app/utils/is-valid-coordinates.js +1 -0
  189. package/app/utils/is-video-file.js +1 -0
  190. package/app/utils/is-waypoint-record.js +1 -0
  191. package/app/utils/ison.js +1 -0
  192. package/app/utils/isset.js +1 -0
  193. package/app/utils/last.js +1 -0
  194. package/app/utils/lazy-load-script.js +1 -0
  195. package/app/utils/leaflet-icon.js +1 -0
  196. package/app/utils/leaflet-points-from-coordinates.js +1 -0
  197. package/app/utils/load-engines.js +1 -0
  198. package/app/utils/load-extensions.js +1 -0
  199. package/app/utils/macros/group-by.js +1 -0
  200. package/app/utils/make-dataset.js +1 -0
  201. package/app/utils/map-engines.js +1 -0
  202. package/app/utils/mock-response.js +1 -0
  203. package/app/utils/numbers-only.js +1 -0
  204. package/app/utils/past-tense.js +1 -0
  205. package/app/utils/path-to-route.js +1 -0
  206. package/app/utils/polyline.js +1 -0
  207. package/app/utils/range.js +1 -0
  208. package/app/utils/refresh-route.js +1 -0
  209. package/app/utils/replace-table-row.js +1 -0
  210. package/app/utils/reverse-point.js +1 -0
  211. package/app/utils/serialize/normalize-polymorphic-type-within-hash.js +1 -0
  212. package/app/utils/serialize/normalize-polymorphic-type.js +1 -0
  213. package/app/utils/serialize/normalize-relations-with-hash.js +1 -0
  214. package/app/utils/set-column-filter-options.js +1 -0
  215. package/app/utils/strip-html.js +1 -0
  216. package/app/utils/to-leaflet-bounds.js +1 -0
  217. package/app/utils/to-model.js +1 -0
  218. package/app/utils/waypoint-label.js +1 -0
  219. package/app/utils/with-default-value.js +1 -0
  220. package/app/utils/words.js +1 -0
  221. package/config/environment.js +5 -0
  222. package/index.js +26 -0
  223. package/package.json +117 -0
  224. package/pnpm-lock.yaml +12750 -0
@@ -0,0 +1,648 @@
1
+ import Service from '@ember/service';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { get, set, setProperties } from '@ember/object';
5
+ import { isBlank } from '@ember/utils';
6
+ import { dasherize } from '@ember/string';
7
+ import { isArray } from '@ember/array';
8
+ import { assign } from '@ember/polyfills';
9
+ import { singularize, pluralize } from 'ember-inflector';
10
+ import { task } from 'ember-concurrency';
11
+ import { storageFor } from 'ember-local-storage';
12
+ import { intervalToDuration, parseISO } from 'date-fns';
13
+ import config from 'ember-get-config';
14
+ import corslite from '../utils/corslite';
15
+ import getMimeType from '../utils/get-mime-type';
16
+ import download from '../utils/download';
17
+ import getUserOptions from '../utils/get-user-options';
18
+ import fetch from 'fetch';
19
+
20
+ export default class FetchService extends Service {
21
+ /**
22
+ * Creates an instance of FetchService.
23
+ * @memberof FetchService
24
+ */
25
+ constructor() {
26
+ super(...arguments);
27
+
28
+ this.headers = this.getHeaders();
29
+ this.host = get(config, 'API.host');
30
+ this.namespace = get(config, 'API.namespace');
31
+ }
32
+
33
+ /**
34
+ * Mutable headers property.
35
+ *
36
+ * @var {Array}
37
+ */
38
+ @tracked headers;
39
+
40
+ /**
41
+ * Mutable namespace property.
42
+ *
43
+ * @var {String}
44
+ */
45
+ @tracked namespace;
46
+
47
+ /**
48
+ * Mutable host property.
49
+ *
50
+ * @var {String}
51
+ */
52
+ @tracked host;
53
+
54
+ /**
55
+ * Gets headers that should be sent with request.
56
+ *
57
+ * @return {Object}
58
+ */
59
+ getHeaders() {
60
+ const headers = {};
61
+ const isAuthenticated = this.session.isAuthenticated;
62
+ const userId = this.session.data.authenticated.user;
63
+ const userOptions = getUserOptions();
64
+ const isSandbox = get(userOptions, `${userId}:sandbox`) === true;
65
+ const testKey = get(userOptions, `${userId}:testKey`);
66
+
67
+ headers['Content-Type'] = 'application/json';
68
+
69
+ if (isAuthenticated) {
70
+ headers['Authorization'] = `Bearer ${this.session.data.authenticated.token}`;
71
+ }
72
+
73
+ if (isAuthenticated && isSandbox) {
74
+ headers['Access-Console-Sandbox'] = true;
75
+ }
76
+
77
+ if (isAuthenticated && !isBlank(testKey)) {
78
+ headers['Access-Console-Sandbox-Key'] = testKey;
79
+ }
80
+
81
+ return headers;
82
+ }
83
+
84
+ /**
85
+ * Updates headers property before making request.
86
+ *
87
+ * @return {FetchService}
88
+ * @memberof FetchService
89
+ */
90
+ refreshHeaders() {
91
+ this.headers = this.getHeaders();
92
+
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ * Allows namespace to be set before making fetch request.
98
+ *
99
+ * @param {String} namespace
100
+ * @return {FetchService}
101
+ * @memberof FetchService
102
+ */
103
+ setNamespace(namespace) {
104
+ this.namespace = namespace;
105
+
106
+ return this;
107
+ }
108
+
109
+ /**
110
+ * Allows host to be set before making fetch request.
111
+ *
112
+ * @param {String} host
113
+ * @return {FetchService}
114
+ * @memberof FetchService
115
+ */
116
+ setHost(host) {
117
+ this.host = host;
118
+
119
+ return this;
120
+ }
121
+
122
+ /**
123
+ * Credentials
124
+ *
125
+ * @var {String}
126
+ */
127
+ credentials = 'include';
128
+
129
+ /**
130
+ * Inject the `store` service
131
+ *
132
+ * @var {Service}
133
+ */
134
+ @service store;
135
+
136
+ /**
137
+ * Inject the `session` service
138
+ *
139
+ * @var {Service}
140
+ */
141
+ @service session;
142
+
143
+ /**
144
+ * Inject the `currentUser` service
145
+ *
146
+ * @var {Service}
147
+ */
148
+ @service currentUser;
149
+
150
+ /**
151
+ * Inject the `notifications` service
152
+ *
153
+ * @var {Service}
154
+ */
155
+ @service notifications;
156
+
157
+ /**
158
+ * Local cache for some static requests
159
+ *
160
+ * @var StorageObject
161
+ */
162
+ @storageFor('local-cache') localCache;
163
+
164
+ /**
165
+ * Normalizes a model response from fetch to a ember data model
166
+ *
167
+ * @param {Object} payload A response from a network request
168
+ * @param {String} modelType The type of model to be normalized too
169
+ *
170
+ * @return {Model} An ember model
171
+ */
172
+ normalizeModel(payload, modelType = null) {
173
+ if (modelType === null) {
174
+ const modelTypeKeys = Object.keys(payload);
175
+ modelType = modelTypeKeys.length ? modelTypeKeys.firstObject : false;
176
+ }
177
+
178
+ if (typeof modelType !== 'string') {
179
+ return payload;
180
+ }
181
+
182
+ const type = dasherize(singularize(modelType));
183
+
184
+ if (isArray(payload)) {
185
+ return payload.map((instance) => this.store.push(this.store.normalize(type, instance)));
186
+ }
187
+
188
+ if (isArray(payload[modelType])) {
189
+ return payload[modelType].map((instance) => this.store.push(this.store.normalize(type, instance)));
190
+ }
191
+
192
+ if (!isBlank(payload) && isBlank(payload[modelType])) {
193
+ return this.jsonToModel(payload, type);
194
+ }
195
+
196
+ return this.store.push(this.store.normalize(type, payload[modelType]));
197
+ }
198
+
199
+ /**
200
+ * Normalizes a model response from a JSON object or string
201
+ *
202
+ * @param {Object} payload A response from a network request
203
+ * @param {String} modelType The type of model to be normalized too
204
+ *
205
+ * @return {Model} An ember model
206
+ */
207
+ jsonToModel(attributes = {}, modelType) {
208
+ if (typeof attributes === 'string') {
209
+ attributes = JSON.parse(attributes);
210
+ }
211
+
212
+ const type = dasherize(modelType);
213
+ const normalized = this.store.push(this.store.normalize(type, attributes));
214
+
215
+ return normalized;
216
+ }
217
+
218
+ /**
219
+ * Parses the JSON returned by a network request
220
+ *
221
+ * @param {Object} response A response from a network request
222
+ * @return {Object} The parsed JSON, status from the response
223
+ *
224
+ * @return {Promise}
225
+ */
226
+ parseJSON(response) {
227
+ return new Promise((resolve, reject) =>
228
+ response
229
+ .json()
230
+ .then((json) =>
231
+ resolve({
232
+ statusText: response.statusText,
233
+ status: response.status,
234
+ ok: response.ok,
235
+ json,
236
+ })
237
+ )
238
+ .catch(() => {
239
+ reject(new Error('Oops! Something went wrong when handling your request.'));
240
+ })
241
+ );
242
+ }
243
+
244
+ /**
245
+ * The base request method
246
+ *
247
+ * @param {String} path
248
+ * @param {String} method
249
+ * @param {Object} data
250
+ * @param {Object} options
251
+ *
252
+ * @return {Promise}
253
+ */
254
+ request(path, method = 'GET', data = {}, options = {}) {
255
+ const headers = assign(this.getHeaders(), options.headers ?? {});
256
+
257
+ return new Promise((resolve, reject) => {
258
+ return fetch(options.externalRequest === true ? path : `${options.host || this.host}/${options.namespace || this.namespace}/${path}`, {
259
+ method,
260
+ mode: options.mode || 'cors',
261
+ credentials: options.credentials || this.credentials,
262
+ headers,
263
+ ...data,
264
+ })
265
+ .then(this.parseJSON)
266
+ .then((response) => {
267
+ // console.log('[fetch:response]', response);
268
+ if (response.ok) {
269
+ if (options.normalizeToEmberData) {
270
+ const normalized = this.normalizeModel(response.json, options.normalizeModelType);
271
+
272
+ if (typeof options.onSuccess === 'function') {
273
+ options.onSuccess(normalized);
274
+ }
275
+
276
+ return resolve(normalized);
277
+ }
278
+
279
+ if (typeof options.onSuccess === 'function') {
280
+ options.onSuccess(response.json);
281
+ }
282
+
283
+ return resolve(response.json);
284
+ }
285
+
286
+ if (typeof options.onError === 'function') {
287
+ options.onError(response.json);
288
+ }
289
+
290
+ if (options.rawError) {
291
+ return reject(response.json);
292
+ }
293
+
294
+ if (isArray(response.json.errors)) {
295
+ return reject(new Error(response.json.errors ? response.json.errors.firstObject : response.statusText));
296
+ }
297
+
298
+ if (response.json.error && typeof response.json.error) {
299
+ return reject(new Error(response.json.error));
300
+ }
301
+
302
+ if (response.json.message && typeof response.json.message) {
303
+ return reject(new Error(response.json.message));
304
+ }
305
+
306
+ return reject(response.json);
307
+ })
308
+ .catch(reject);
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Makes a GET request with fetch
314
+ *
315
+ * @param {String} path
316
+ * @param {Object} query
317
+ * @param {Object} options
318
+ *
319
+ * @return {Promise}
320
+ */
321
+ get(path, query = {}, options = {}) {
322
+ // handle if want to request from cache
323
+ if (options.fromCache === true) {
324
+ return this.cachedGet(...arguments);
325
+ }
326
+
327
+ const urlParams = !isBlank(query) ? new URLSearchParams(query).toString() : '';
328
+
329
+ return this.request(`${path}${urlParams ? '?' + urlParams : ''}`, 'GET', {}, options);
330
+ }
331
+
332
+ /**
333
+ * Makes a GET request with fetch, but if the fetch is stored in local cache,
334
+ * retrieve from storage to prevent unnecessary netwrok request
335
+ *
336
+ * @param {String} path
337
+ * @param {Object} query
338
+ * @param {Object} options
339
+ *
340
+ * @return {Promise}
341
+ */
342
+ cachedGet(path, query = {}, options = {}) {
343
+ const pathKey = dasherize(path);
344
+ const pathKeyVersion = new Date().toISOString();
345
+
346
+ const request = () => {
347
+ return this.get(path, query, options).then((response) => {
348
+ // cache the response
349
+ this.localCache.set(pathKey, response);
350
+ this.localCache.set(`${pathKey}-version`, pathKeyVersion);
351
+
352
+ // return response
353
+ return response;
354
+ });
355
+ };
356
+
357
+ // check to see if in storage already
358
+ if (this.localCache.get(pathKey)) {
359
+ return new Promise((resolve) => {
360
+ // get cached data
361
+ const data = this.localCache.get(pathKey);
362
+
363
+ // get the path key version value
364
+ const version = this.localCache.get(`${pathKey}-version`);
365
+ const expirationInterval = options.expirationInterval ?? 3;
366
+ const expirationIntervalUnit = pluralize(options.expirationIntervalUnit ?? 'days');
367
+
368
+ // calculate duration between cache version and now
369
+ const duration = intervalToDuration({
370
+ start: parseISO(version),
371
+ end: new Date(),
372
+ });
373
+ // determine if we should expire cache
374
+ const shouldExpire = duration[expirationIntervalUnit] > expirationInterval;
375
+
376
+ // if the version is older than 3 days clear it
377
+ if (!version || shouldExpire || options.clearData === true) {
378
+ this.flushRequestCache(path);
379
+ return request();
380
+ }
381
+
382
+ if (options.normalizeToEmberData) {
383
+ return resolve(this.normalizeModel(data, options.normalizeModelType));
384
+ }
385
+
386
+ // return cached response
387
+ return resolve(data);
388
+ });
389
+ }
390
+
391
+ // if no cached data request from server
392
+ return request();
393
+ }
394
+
395
+ flushRequestCache(path) {
396
+ const pathKey = dasherize(path);
397
+
398
+ this.localCache.set(pathKey, undefined);
399
+ this.localCache.set(`${pathKey}-version`, undefined);
400
+ }
401
+
402
+ shouldResetCache() {
403
+ const consoleVersion = this.localCache.get('console-version');
404
+
405
+ if (!consoleVersion || consoleVersion !== config.APP.version) {
406
+ this.localCache.clear();
407
+ this.localCache.set('console-version', config.APP.version);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Makes a POST request with fetch
413
+ *
414
+ * @param {String} path
415
+ * @param {Object} data
416
+ * @param {Object} options
417
+ *
418
+ * @return {Promise}
419
+ */
420
+ post(path, data = {}, options = {}) {
421
+ return this.request(path, 'POST', { body: JSON.stringify(data) }, options);
422
+ }
423
+
424
+ /**
425
+ * Makes a PUT request with fetch
426
+ *
427
+ * @param {String} path
428
+ * @param {Object} data
429
+ * @param {Object} options
430
+ *
431
+ * @return {Promise}
432
+ */
433
+ put(path, data = {}, options = {}) {
434
+ return this.request(path, 'PUT', { body: JSON.stringify(data) }, options);
435
+ }
436
+
437
+ /**
438
+ * Makes a DELETE request with fetch
439
+ *
440
+ * @param {String} path
441
+ * @param {Object} data
442
+ * @param {Object} options
443
+ *
444
+ * @return {Promise}
445
+ */
446
+ delete(path, data = {}, options = {}) {
447
+ return this.request(path, 'DELETE', { body: JSON.stringify(data) }, options);
448
+ }
449
+
450
+ /**
451
+ * Makes a PATCH request with fetch
452
+ * @param {String} path
453
+ * @param {Object} data
454
+ * @param {Object} options
455
+ *
456
+ * @return {Promise}
457
+ */
458
+ patch(path, data = {}, options = {}) {
459
+ return this.request(path, 'PATCH', { body: JSON.stringify(data) }, options);
460
+ }
461
+
462
+ /**
463
+ * Makes a upload request with fetch
464
+ *
465
+ * @param {String} path
466
+ * @param {Array} files
467
+ * @param {Object} options
468
+ *
469
+ * @return {Promise}
470
+ */
471
+ upload(path, files = [], options = {}) {
472
+ const body = new FormData();
473
+ files.forEach((file) => {
474
+ body.append('file', file);
475
+ });
476
+ return this.request(path, 'POST', { body }, options);
477
+ }
478
+
479
+ /**
480
+ * Sends request to routing service.
481
+ *
482
+ * @param {Array} coordinates
483
+ * @param {Object} query
484
+ * @param {String} service
485
+ * @param {String} profile
486
+ * @param {String} version
487
+ */
488
+ routing(coordinates, query = {}, options = {}) {
489
+ let service = options?.service ?? 'trip';
490
+ let profile = options?.profile ?? 'driving';
491
+ let version = options?.version ?? 'v1';
492
+ let host = options?.host ?? `https://${options?.subdomain ?? 'routing'}.fleetbase.io`;
493
+ let route = coordinates.map((coords) => coords.join(',')).join(';');
494
+ let params = !isBlank(query) ? new URLSearchParams(query).toString() : '';
495
+ let path = `${host}/${service}/${version}/${profile}/${route}`;
496
+ let url = `${path}${params ? '?' + params : ''}`;
497
+
498
+ return new Promise((resolve, reject) => {
499
+ corslite(url, (container, xhr) => {
500
+ if (!xhr || !xhr.response) {
501
+ reject(new Error('Request failed.'));
502
+ return;
503
+ }
504
+
505
+ let response = xhr.response;
506
+ let isJson = typeof response === 'string' && response.startsWith('{');
507
+
508
+ resolve(isJson ? JSON.parse(response) : response);
509
+ });
510
+ });
511
+ }
512
+
513
+ /**
514
+ * Concurrency task to handle a file upload
515
+ *
516
+ * @void
517
+ */
518
+ @(task(function* (file, params = {}, callback, errorCallback) {
519
+ const { queue } = file;
520
+ const headers = this.getHeaders();
521
+
522
+ // remove Content-Type header
523
+ delete headers['Content-Type'];
524
+
525
+ try {
526
+ const upload = yield file
527
+ .upload(`${get(config, 'API.host')}/${get(config, 'API.namespace')}/files/upload`, {
528
+ data: {
529
+ ...params,
530
+ file_size: file.size,
531
+ },
532
+ withCredentials: true,
533
+ headers,
534
+ })
535
+ .then((response) => response.json());
536
+
537
+ const model = this.store.push(this.store.normalize('file', upload.file));
538
+ set(file, 'model', model);
539
+
540
+ if (typeof callback === 'function') {
541
+ callback(model);
542
+ }
543
+
544
+ return model;
545
+ } catch (error) {
546
+ queue.remove(file);
547
+ this.notifications.serverError(error, `Upload failed.`);
548
+
549
+ if (typeof errorCallback === 'function') {
550
+ errorCallback(error);
551
+ }
552
+ }
553
+ })
554
+ .maxConcurrency(3)
555
+ .enqueue())
556
+ uploadFile;
557
+
558
+ /**
559
+ * Downloads blob of the request path to user
560
+ *
561
+ * @param {String} path
562
+ * @param {Object} query
563
+ * @param {Object} options
564
+ *
565
+ * @return {Promise}
566
+ */
567
+ download(path, query = {}, options = {}) {
568
+ const headers = assign(this.getHeaders(), options.headers ?? {});
569
+
570
+ return new Promise((resolve, reject) => {
571
+ return fetch(`${options.host || this.host}/${options.namespace || this.namespace}/${path}?${!isBlank(query) ? new URLSearchParams(query).toString() : ''}`, {
572
+ method: 'GET',
573
+ credentials: options.credentials || this.credentials,
574
+ headers,
575
+ })
576
+ .then((response) => {
577
+ options.fileName = this.getFilenameFromResponse(response, options.fileName);
578
+ options.mimeType = this.getMimeTypeFromResponse(response, options.mimeType);
579
+
580
+ if (!options.mimeType) {
581
+ options.mimeType = getMimeType(options.fileName);
582
+ }
583
+
584
+ return response;
585
+ })
586
+ .then((response) => response.blob())
587
+ .then((blob) => resolve(download(blob, options.fileName, options.mimeType)))
588
+ .catch((error) => {
589
+ reject(error);
590
+ });
591
+ });
592
+ }
593
+
594
+ getFilenameFromResponse(response, defaultFilename = null) {
595
+ const contentDisposition = response.headers.get('content-disposition');
596
+ let fileName = defaultFilename;
597
+
598
+ if (contentDisposition) {
599
+ const results = /filename=(.*)/.exec(contentDisposition);
600
+
601
+ if (isArray(results) && results.length > 1) {
602
+ fileName = results[1];
603
+
604
+ // clean fileName
605
+ fileName = fileName.replaceAll('"', '');
606
+ }
607
+ }
608
+
609
+ return fileName;
610
+ }
611
+
612
+ getMimeTypeFromResponse(response, defaultMimeType = null) {
613
+ const contentType = response.headers.get('content-type');
614
+ let mimeType = defaultMimeType;
615
+
616
+ if (contentType) {
617
+ const results = /(.*)?;/.exec(contentType);
618
+
619
+ if (isArray(results) && results.length > 1) {
620
+ mimeType = results[1];
621
+ }
622
+ }
623
+
624
+ return mimeType;
625
+ }
626
+
627
+ fetchOrderConfigurations(params = {}) {
628
+ return new Promise((resolve, reject) => {
629
+ this.request('fleet-ops/order-configs/get-installed', params)
630
+ .then((configs) => {
631
+ const serialized = [];
632
+
633
+ for (let i = 0; i < configs.length; i++) {
634
+ const config = configs.objectAt(i);
635
+ const normalizedConfig = this.store.normalize('order-config', config);
636
+ const serializedConfig = this.store.push(normalizedConfig);
637
+
638
+ serialized.pushObject(serializedConfig);
639
+ }
640
+
641
+ resolve(serialized);
642
+ })
643
+ .catch((error) => {
644
+ reject(error);
645
+ });
646
+ });
647
+ }
648
+ }