@mapcreator/api 5.0.0-alpha.84 → 5.0.0-alpha.86

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 (61) hide show
  1. package/LICENSE +29 -29
  2. package/README.md +86 -86
  3. package/cjs/api/choropleth.d.ts +4 -81
  4. package/cjs/api/choropleth.d.ts.map +1 -1
  5. package/cjs/api/choropleth.js +17 -26
  6. package/cjs/api/choropleth.js.map +1 -1
  7. package/cjs/api/font.d.ts +3 -0
  8. package/cjs/api/font.d.ts.map +1 -1
  9. package/cjs/api/font.js +0 -3
  10. package/cjs/api/font.js.map +1 -1
  11. package/cjs/api/insetMap.d.ts.map +1 -1
  12. package/cjs/api/insetMap.js +3 -5
  13. package/cjs/api/insetMap.js.map +1 -1
  14. package/cjs/api/jobRevision.d.ts.map +1 -1
  15. package/cjs/api/jobRevision.js +4 -6
  16. package/cjs/api/jobRevision.js.map +1 -1
  17. package/cjs/api/resources.d.ts +6 -0
  18. package/cjs/api/resources.d.ts.map +1 -1
  19. package/cjs/api/resources.js +5 -1
  20. package/cjs/api/resources.js.map +1 -1
  21. package/cjs/oauth.d.ts +9 -6
  22. package/cjs/oauth.d.ts.map +1 -1
  23. package/cjs/oauth.js +28 -203
  24. package/cjs/oauth.js.map +1 -1
  25. package/cjs/utils.d.ts.map +1 -1
  26. package/cjs/utils.js +3 -3
  27. package/cjs/utils.js.map +1 -1
  28. package/esm/api/choropleth.d.ts +4 -81
  29. package/esm/api/choropleth.d.ts.map +1 -1
  30. package/esm/api/choropleth.js +16 -23
  31. package/esm/api/choropleth.js.map +1 -1
  32. package/esm/api/font.d.ts +3 -0
  33. package/esm/api/font.d.ts.map +1 -1
  34. package/esm/api/font.js +1 -4
  35. package/esm/api/font.js.map +1 -1
  36. package/esm/api/insetMap.d.ts.map +1 -1
  37. package/esm/api/insetMap.js +4 -6
  38. package/esm/api/insetMap.js.map +1 -1
  39. package/esm/api/jobRevision.d.ts.map +1 -1
  40. package/esm/api/jobRevision.js +4 -6
  41. package/esm/api/jobRevision.js.map +1 -1
  42. package/esm/api/resources.d.ts +6 -0
  43. package/esm/api/resources.d.ts.map +1 -1
  44. package/esm/api/resources.js +5 -1
  45. package/esm/api/resources.js.map +1 -1
  46. package/esm/oauth.d.ts +9 -6
  47. package/esm/oauth.d.ts.map +1 -1
  48. package/esm/oauth.js +27 -201
  49. package/esm/oauth.js.map +1 -1
  50. package/esm/utils.d.ts.map +1 -1
  51. package/esm/utils.js +4 -4
  52. package/esm/utils.js.map +1 -1
  53. package/package.json +80 -80
  54. package/src/README.md +126 -126
  55. package/src/api/choropleth.ts +44 -140
  56. package/src/api/font.ts +12 -4
  57. package/src/api/insetMap.ts +4 -5
  58. package/src/api/jobRevision.ts +4 -5
  59. package/src/api/resources.ts +8 -0
  60. package/src/oauth.ts +28 -258
  61. package/src/utils.ts +4 -4
@@ -1,14 +1,6 @@
1
- import { type ApiError, type ApiSuccess, type Flatten, type Revivers, getSearchParams, request } from '../utils.js';
2
-
3
- import type { RequireAtLeastOne } from 'type-fest';
1
+ import { type ApiError, type ApiSuccess, Flatten, Revivers, getSearchParams, request } from '../utils.js';
4
2
  import type { Polygon } from 'geojson';
5
-
6
- export const boundingBoxRevivers: Revivers<
7
- { data: { bounding_box: string } } & Omit<ApiSuccess, 'data'> | ApiError,
8
- { boundingBox: Polygon }
9
- > = {
10
- boundingBox: (data: { bounding_box: string }) => JSON.parse(data.bounding_box) as Polygon,
11
- };
3
+ import { RequireAtLeastOne } from 'type-fest';
12
4
 
13
5
  export type ApiSearchPoint = {
14
6
  lat: number;
@@ -22,6 +14,23 @@ export type ApiSearchBounds = {
22
14
  max_lng: number;
23
15
  };
24
16
 
17
+ type ApiSingleOrGroupedArea = {
18
+ id: number;
19
+ title: string;
20
+ subtitle: string;
21
+ svg_preview: string;
22
+ bounding_box: string;
23
+ is_group: boolean;
24
+ vector_source: string | null;
25
+ source_layer: string | null;
26
+ feature_id: number | null;
27
+ properties: Record<string, string> | null;
28
+ };
29
+
30
+ type ApiSingleOrGroupedAreaArray = {
31
+ data: ApiSingleOrGroupedArea[];
32
+ } & Omit<ApiSuccess, 'data'> | ApiError;
33
+
25
34
  type SingleOrGroupedAreaBase = {
26
35
  id: number;
27
36
  title: string;
@@ -38,51 +47,14 @@ export type GroupedArea = SingleOrGroupedAreaBase & {
38
47
  // TODO don't export this once search on click is out
39
48
  export type SingleArea = SingleOrGroupedAreaBase & {
40
49
  isGroup: false;
41
- properties: Record<string, string>;
42
50
  vectorSource: string;
43
51
  sourceLayer: string;
44
52
  featureId: number;
53
+ properties: Record<string, string>;
45
54
  };
46
55
 
47
- export type SingleOrGroupedArea = {
48
- id: number;
49
- title: string;
50
- subtitle: string;
51
- svgPreview: string;
52
- boundingBox: Polygon;
53
- isGroup: boolean;
54
- properties: Record<string, string> | null;
55
- vectorSource: string | null;
56
- sourceLayer: string | null;
57
- featureId: number | null;
58
- };
59
-
60
- export type ApiSingleOrGroupedArea = {
61
- data: {
62
- id: number;
63
- title: string;
64
- subtitle: string;
65
- svg_preview: string;
66
- bounding_box: string;
67
- is_group: boolean;
68
- properties: string | null;
69
- vector_source: string | null;
70
- source_layer: string | null;
71
- feature_id: number | null;
72
- };
73
- } & Omit<ApiSuccess, 'data'> | ApiError;
74
-
75
- export type ApiSingleOrGroupedAreaData = Flatten<Exclude<ApiSingleOrGroupedArea, ApiError>['data']>;
76
-
77
- export const singleOrGroupedAreaRevivers: Revivers<ApiSingleOrGroupedArea, SingleOrGroupedArea> = {
78
- ...boundingBoxRevivers,
79
- properties: (data: ApiSingleOrGroupedAreaData) =>
80
- (data.properties != null ? JSON.parse(data.properties) as Record<string, string> : null),
81
- };
56
+ export type SingleOrGroupedArea = SingleArea | GroupedArea;
82
57
 
83
- /**
84
- * TODO When SAGA search on click is implemented, remove mode and make searchBounds required
85
- */
86
58
  export async function searchSingleOrGroupedAreas(
87
59
  language: string,
88
60
  search: RequireAtLeastOne<{
@@ -92,22 +64,29 @@ export async function searchSingleOrGroupedAreas(
92
64
  }>,
93
65
  mode: 'polygon' | 'group' | 'both' = 'both',
94
66
  ): Promise<SingleOrGroupedArea[]> {
95
- const pathname = '/v1/choropleth/polygons/search';
96
- const query = getSearchParams({
97
- language,
98
- ...search.searchBounds,
99
- ...(search.query && { query: search.query }),
100
- ...(search.searchPoint && { point: search.searchPoint }),
101
- mode,
67
+ /**
68
+ * TODO When SAGA search on click is implemented, remove mode and make searchBounds required
69
+ */
70
+ return request<ApiSingleOrGroupedAreaArray, SingleOrGroupedArea>(
71
+ `/v1/choropleth/polygons/search?${getSearchParams({
72
+ language,
73
+ ...search.searchBounds,
74
+ ...(search.query && { query: search.query }),
75
+ ...(search.searchPoint && { point: search.searchPoint }),
76
+ mode,
77
+ })}`,
78
+ ).then(result => {
79
+ result.forEach(
80
+ elem => {
81
+ elem.boundingBox = JSON.parse(elem.boundingBox as unknown as string) as Polygon;
82
+ if (!elem.isGroup) {
83
+ elem.properties = JSON.parse(elem.properties as unknown as string) as Record<string, string>;
84
+ }
85
+ },
86
+ );
87
+
88
+ return result;
102
89
  });
103
- const path = `${pathname}?${query}`;
104
- const options = { revivers: singleOrGroupedAreaRevivers };
105
-
106
- type ApiSingleOrGroupedAreaArray = {
107
- data: ApiSingleOrGroupedAreaData[];
108
- } & Omit<ApiSuccess, 'data'> | ApiError;
109
-
110
- return request<ApiSingleOrGroupedAreaArray, SingleOrGroupedArea>(path, null, null, options);
111
90
  }
112
91
 
113
92
  export type GroupedAreaChild = {
@@ -135,7 +114,7 @@ type ApiGroupedAreaChild = {
135
114
  export type ApiGroupedAreaChildData = Flatten<Exclude<ApiGroupedAreaChild, ApiError>['data']>;
136
115
 
137
116
  export const groupedAreaChildRevivers: Revivers<ApiGroupedAreaChild, GroupedAreaChild> = {
138
- ...boundingBoxRevivers,
117
+ boundingBox: (data: ApiGroupedAreaChildData) => JSON.parse(data.bounding_box) as Polygon,
139
118
  properties: (data: ApiGroupedAreaChildData) => JSON.parse(data.properties) as Record<string, string>,
140
119
  };
141
120
 
@@ -151,78 +130,3 @@ export async function groupedAreaChildren(groupId: number, language: string): Pr
151
130
 
152
131
  return request<ApiApiGroupedAreaChildArray, GroupedAreaChild>(path, null, null, options);
153
132
  }
154
-
155
- export type MatchedGroup = {
156
- id: number;
157
- sml: number;
158
- childrenCount: number;
159
- matchField: string;
160
- boundingBox: Polygon;
161
- property: string;
162
- name: string;
163
- };
164
-
165
- export type ApiMatchedGroup = {
166
- data: {
167
- id: number;
168
- sml: number;
169
- children_count: number;
170
- match_field: string;
171
- bounding_box: string;
172
- property: string;
173
- name: string;
174
- };
175
- } & Omit<ApiSuccess, 'data'> | ApiError;
176
-
177
- export type ApiMatchedGroupData = Flatten<Exclude<ApiMatchedGroup, ApiError>['data']>;
178
-
179
- export async function getGroupsByDataSample(
180
- sample: Record<string, string[]>,
181
- language: string,
182
- rowCount: number,
183
- ): Promise<MatchedGroup[]> {
184
- const path = `/v1/choropleth/groups/sample`;
185
- const options = { revivers: boundingBoxRevivers };
186
-
187
- type ApiMatchedGroupArray = {
188
- data: ApiMatchedGroupData[];
189
- } & Omit<ApiSuccess, 'data'> | ApiError;
190
-
191
- return request<ApiMatchedGroupArray, MatchedGroup>(path, { sample, language, row_count: rowCount }, null, options);
192
- }
193
-
194
- export type BoundPolygon = {
195
- index: number;
196
- inputName: string;
197
- id: number;
198
- sml: number;
199
- name: string;
200
- };
201
-
202
- export type ApiBoundPolygon = {
203
- data: {
204
- index: number;
205
- input_name: string;
206
- id: number;
207
- sml: number;
208
- name: string;
209
- };
210
- } & Omit<ApiSuccess, 'data'> | ApiError;
211
-
212
- export type ApiBoundPolygonData = Flatten<Exclude<ApiBoundPolygon, ApiError>['data']>;
213
-
214
- export async function getBoundPolygons(
215
- groupId: number,
216
- property: string,
217
- data: string[],
218
- language: string,
219
- ): Promise<BoundPolygon[]> {
220
- const path = `/v1/choropleth/groups/bind`;
221
- const body = { group_id: groupId, property, data, language };
222
-
223
- type ApiBoundPolygonArray = {
224
- data: ApiBoundPolygonData[];
225
- } & Omit<ApiSuccess, 'data'> | ApiError;
226
-
227
- return request<ApiBoundPolygonArray, BoundPolygon>(path, body);
228
- }
package/src/api/font.ts CHANGED
@@ -1,10 +1,21 @@
1
- import { type ApiCommonData, type ApiError, type ApiSuccess, type Flatten, type Revivers, getSearchParams, request } from '../utils.js';
1
+ import {
2
+ type ApiCommonData,
3
+ type ApiError,
4
+ type ApiSuccess,
5
+ type Flatten,
6
+ type Revivers,
7
+ getSearchParams,
8
+ request,
9
+ } from '../utils.js';
2
10
 
3
11
  export type Font = {
4
12
  id: number;
5
13
  fontFamilyId: number;
6
14
  name: string;
7
15
  label: string;
16
+ weight: number;
17
+ style: string;
18
+ stretch: string;
8
19
  };
9
20
 
10
21
  export type ApiFont = {
@@ -29,9 +40,6 @@ type FontSearchOptions = {
29
40
  };
30
41
 
31
42
  export const fontRevivers: Revivers<ApiFont, Font> = {
32
- style: undefined,
33
- stretch: undefined,
34
- weight: undefined,
35
43
  order: undefined,
36
44
  };
37
45
 
@@ -1,4 +1,4 @@
1
- import { apiHost, authenticate, token } from '../oauth.js';
1
+ import { apiHost, authenticate, getAuthorizationHeaders, token } from '../oauth.js';
2
2
  import {
3
3
  APIError,
4
4
  type ApiCommonData,
@@ -63,10 +63,9 @@ export async function getInsetMap(insetMapId: number): Promise<InsetMap> {
63
63
 
64
64
  export async function getInsetMapTopoJson<TopoJSON>(insetMapId: number): Promise<TopoJSON> {
65
65
  const href = `${apiHost}/v1/inset-maps/${insetMapId}/json`;
66
- const headers = { Accept: 'application/json', ...(token ? { Authorization: token.toString() } : null) };
67
- const response = await fetch(href, { headers }).catch((error: Error) => {
68
- throw new NetworkError(error?.message ?? error);
69
- });
66
+ const headers = getAuthorizationHeaders('GET');
67
+ const response = await fetch(href, { headers, ...!token && { credentials: 'include' } })
68
+ .catch((error: Error) => { throw new NetworkError(error?.message ?? error) });
70
69
 
71
70
  if (response.ok) {
72
71
  return response.json().catch(() => {
@@ -1,5 +1,5 @@
1
+ import { apiHost, authenticate, getAuthorizationHeaders, token } from '../oauth.js';
1
2
  import { type ApiLayerData, type Layer, layerRevivers } from './layer.js';
2
- import { apiHost, authenticate, token } from '../oauth.js';
3
3
  import type { ApiMapstyleSetData } from './mapstyleSet.js';
4
4
  import type { ApiLanguageData } from './language.js';
5
5
  import {
@@ -221,10 +221,9 @@ export async function getJobRevisionOutput(jobId: number): Promise<JobRevisionOu
221
221
  await createJobRevisionBuild(jobId);
222
222
 
223
223
  const href = `${apiHost}/v1/jobs/${jobId}/revisions/${lastJobRevision}/result/output`;
224
- const headers = { ...(token ? { Authorization: token.toString() } : null) };
225
- const response = await fetch(href, { headers }).catch((error: Error) => {
226
- throw new NetworkError(error?.message ?? error);
227
- });
224
+ const headers = getAuthorizationHeaders('GET');
225
+ const response = await fetch(href, { headers, ...!token && { credentials: 'include' } })
226
+ .catch((error: Error) => { throw new NetworkError(error?.message ?? error) });
228
227
 
229
228
  if (response.ok) {
230
229
  const blob = await response.blob().catch(() => {
@@ -1,6 +1,7 @@
1
1
  import { type ApiOrganisationData, type Organisation, organisationRevivers } from './organisation.js';
2
2
  import { type ApiDimensionSetData, type DimensionSet, dimensionSetRevivers } from './dimensionSet.js';
3
3
  import { type ApiMapstyleSetData, type MapstyleSet, mapstyleSetRevivers } from './mapstyleSet.js';
4
+ import { type ApiFontFamilyData, type FontFamily, fontFamilyRevivers } from './fontFamily.js';
4
5
  import { type ApiDimensionData, type Dimension, dimensionRevivers } from './dimension.js';
5
6
  import { type ApiFeatureData, type Feature, featureRevivers } from './feature.js';
6
7
  import { type ApiJobTypeData, type JobType, jobTypeRevivers } from './jobType.js';
@@ -10,6 +11,7 @@ import { type ApiSvgSetData, type SvgSet, svgSetRevivers } from './svgSet.js';
10
11
  import { type ApiLayerData, type Layer, layerRevivers } from './layer.js';
11
12
  import { type ApiColorData, type Color, colorRevivers } from './color.js';
12
13
  import { type ApiUserData, type User, userRevivers } from './user.js';
14
+ import { type ApiFontData, type Font, fontRevivers } from './font.js';
13
15
  import type { CamelCasedProperties } from 'type-fest';
14
16
  import {
15
17
  type ApiCommon,
@@ -62,6 +64,8 @@ export interface Resources {
62
64
  messages: Message[];
63
65
  svgSets: SvgSet[];
64
66
  layers: Layer[];
67
+ fonts: Font[];
68
+ fontFamilies: FontFamily[];
65
69
  }
66
70
 
67
71
  type ApiResources = {
@@ -82,6 +86,8 @@ type ApiResources = {
82
86
  job_types: ApiJobTypeData[];
83
87
  svg_sets: ApiSvgSetData[];
84
88
  layers: ApiLayerData[];
89
+ fonts: ApiFontData[];
90
+ font_families: ApiFontFamilyData[];
85
91
  } & ApiCommonData;
86
92
  } & Omit<ApiSuccess, 'data'> | ApiError;
87
93
 
@@ -129,6 +135,8 @@ export async function loadResources(): Promise<Resources> {
129
135
  messages: allMessages,
130
136
  layers: (raw.layers?.map(processData, getContext(layerRevivers)) ?? []) as Layer[],
131
137
  svgSets: (raw.svgSets?.map(processData, getContext(svgSetRevivers)) ?? []) as SvgSet[],
138
+ fonts: (raw.fonts?.map(processData, getContext(fontRevivers)) ?? []) as Font[],
139
+ fontFamilies: (raw.fontFamilies?.map(processData, getContext(fontFamilyRevivers)) ?? []) as FontFamily[],
132
140
  };
133
141
  }
134
142
 
package/src/oauth.ts CHANGED
@@ -8,27 +8,19 @@ export let token: {
8
8
  toString: () => string;
9
9
  } | null = null;
10
10
 
11
- let apiClientId = '';
12
11
  let callbackUrl = '';
13
- let oauthScopes = ['*'];
14
12
 
15
- const anchorParams = ['access_token', 'token_type', 'expires_in', 'state'];
16
-
17
- const storagePrefix = '_m4n_';
18
- const statePrefix = 'oauth_state_';
19
- const storageName = 'api_token';
20
-
21
- const dummyTokenExpires = new Date('2100-01-01T01:00:00');
13
+ /**
14
+ * Cleanup of previously used data. The code part can be removed in a while.
15
+ */
16
+ for (let i = 0; i < window.localStorage.length; ++i) {
17
+ const key = window.localStorage.key(i);
22
18
 
23
- interface AnchorToken {
24
- access_token: string;
25
- token_type: string;
26
- expires_in: string;
27
- state: string;
19
+ if (key?.startsWith('_m4n_')) {
20
+ window.localStorage.removeItem(key);
21
+ }
28
22
  }
29
23
 
30
- const titleCase = (str: unknown): string => String(str).toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
31
-
32
24
  /**
33
25
  * Setup internal structures to use dummy authentication flow
34
26
  *
@@ -40,9 +32,9 @@ export function initDummyFlow(apiUrl: string, oauthToken: string): void {
40
32
 
41
33
  apiHost = apiUrl.replace(/\/+$/, '');
42
34
  token = {
43
- type: titleCase(parts[0]),
35
+ type: parts[0].toLowerCase().replace(/\b\w/g, c => c.toUpperCase()),
44
36
  token: parts[1],
45
- expires: dummyTokenExpires,
37
+ expires: new Date('2100-01-01T01:00:00'),
46
38
 
47
39
  toString(): string {
48
40
  return `${this.type} ${this.token}`;
@@ -54,261 +46,39 @@ export function initDummyFlow(apiUrl: string, oauthToken: string): void {
54
46
  * Setup internal structures to use implicit authentication flow
55
47
  *
56
48
  * @param {string} apiUrl - Full API URL
57
- * @param {string} clientId - OAuth client id
58
49
  * @param {string} [redirectUrl] - Callback URL
59
- * @param {string[]} [scopes] - A list of required scopes
60
50
  */
61
- export function initImplicitFlow(apiUrl: string, clientId: string, redirectUrl = '', scopes = ['*']): void {
51
+ export function initImplicitFlow(apiUrl: string, redirectUrl = ''): void {
62
52
  apiHost = apiUrl.replace(/\/+$/, '');
63
53
 
64
- apiClientId = String(clientId);
65
54
  callbackUrl = String(redirectUrl || window.location.href.split('#')[0]);
66
- oauthScopes = scopes;
67
-
68
- {
69
- const key = `${storagePrefix}${storageName}`;
70
- const data = window.localStorage.getItem(key);
71
-
72
- if (data) {
73
- try {
74
- const obj = JSON.parse(data) as { type?: unknown; token?: unknown; expires?: unknown };
75
-
76
- if (
77
- typeof obj.type === 'string' &&
78
- typeof obj.token === 'string' &&
79
- typeof obj.expires === 'string' &&
80
- new Date(obj.expires) > new Date()
81
- ) {
82
- token = {
83
- type: titleCase(obj.type),
84
- token: obj.token,
85
- expires: new Date(obj.expires),
86
-
87
- toString(): string {
88
- return `${this.type} ${this.token}`;
89
- },
90
- };
91
- } else {
92
- window.localStorage.removeItem(key);
93
- }
94
- } catch (e) {
95
- /* */
96
- }
97
- }
98
- }
99
-
100
- {
101
- const obj = getAnchorToken();
102
-
103
- if (isAnchorToken(obj)) {
104
- // We'll not go there if anchor contains error and/or message
105
- // This means that anchor parameters will be preserved for the next processing
106
- cleanAnchorParams();
107
-
108
- const expires = new Date(Date.now() + Number(obj.expires_in) * 1000);
109
-
110
- if (isValidState(obj.state) && expires > new Date()) {
111
- token = {
112
- type: titleCase(obj.token_type),
113
- token: obj.access_token,
114
- expires,
115
-
116
- toString(): string {
117
- return `${this.type} ${this.token}`;
118
- },
119
- };
120
-
121
- const key = `${storagePrefix}${storageName}`;
122
- const data = { type: token.type, token: token.token, expires: expires.toUTCString() };
123
-
124
- window.localStorage.setItem(key, JSON.stringify(data));
125
- } else {
126
- // TODO: add some logic to handle this
127
- // throw Error('Invalid state in url');
128
- }
129
- }
130
- }
131
55
 
132
- if (authenticated()) {
133
- const href = sessionStorage.getItem('redirect-url');
56
+ const href = sessionStorage.getItem('redirect-url');
134
57
 
135
- if (href) {
136
- sessionStorage.removeItem('redirect-url');
137
- window.history.replaceState(null, document.title, href);
138
- }
58
+ if (href) {
59
+ sessionStorage.removeItem('redirect-url');
60
+ window.history.replaceState(null, document.title, href);
139
61
  }
140
62
  }
141
63
 
142
- export async function authenticate(): Promise<string> | never {
64
+ export async function authenticate(): Promise<never> {
143
65
  return new Promise(() => {
144
- if (anchorContainsError()) {
145
- console.error(getError());
146
- cleanAnchorParams();
147
- }
148
-
149
- forget();
150
-
151
66
  sessionStorage.setItem('redirect-url', window.location.href);
152
- window.location.assign(buildRedirectUrl());
67
+ window.location.assign(`${apiHost}/login?${new URLSearchParams({ redirect_uri: callbackUrl })}`);
153
68
  });
154
69
  }
155
70
 
156
- export function authenticated(): boolean {
157
- return token != null && token.expires > new Date() && (
158
- token.expires.valueOf() === dummyTokenExpires.valueOf() ||
159
- !!window.localStorage.getItem(`${storagePrefix}${storageName}`)
160
- );
161
- }
162
-
163
- export async function logout(): Promise<void> {
164
- if (token) {
165
- await fetch(`${apiHost}/oauth/logout`, {
166
- method: 'POST',
167
- headers: {
168
- Accept: 'application/json',
169
- Authorization: token.toString(),
170
- },
171
- });
172
- }
173
-
174
- forget();
175
- }
176
-
177
- function forget(): void {
178
- for (let i = 0; i < window.localStorage.length; ++i) {
179
- const key = window.localStorage.key(i);
180
-
181
- if (key?.startsWith(storagePrefix)) {
182
- window.localStorage.removeItem(key);
183
- }
184
- }
185
-
186
- token = null;
187
- }
188
-
189
- function buildRedirectUrl(): string {
190
- const queryParams = new URLSearchParams({
191
- client_id: apiClientId,
192
- redirect_uri: callbackUrl,
193
- response_type: 'token',
194
- scope: oauthScopes.join(' '),
195
- state: generateState(),
196
- });
197
-
198
- return `${apiHost}/oauth/authorize?${queryParams}`;
199
- }
200
-
201
- function getAnchorQuery(): string {
202
- return window.location.hash.replace(/^#\/?/, '');
203
- }
204
-
205
- function getAnchorParams(): Record<string, unknown> {
206
- const query = getAnchorQuery();
207
- // eslint-disable-next-line @stylistic/padding-line-between-statements,@typescript-eslint/no-unsafe-return
208
- return Object.fromEntries(query.split('&').map(pair => pair.split('=').map(decodeURIComponent)));
209
- }
210
-
211
- function getAnchorToken(): Partial<AnchorToken> {
212
- const params = getAnchorParams();
213
-
214
- return Object.fromEntries(Object.entries(params).filter(([key]) => anchorParams.includes(key)));
215
- }
216
-
217
- function isAnchorToken(anchorToken: Partial<AnchorToken>): anchorToken is AnchorToken {
218
- const queryKeys = Object.keys(anchorToken);
219
-
220
- return anchorParams.every(key => queryKeys.includes(key));
221
- }
222
-
223
- function cleanAnchorParams(): void {
224
- const query = window.location.hash.replace(/^#\/?/, '');
225
- const targets = [...anchorParams, 'error', 'message'];
226
- const newHash = query
227
- .split('&')
228
- .filter(pair => !targets.includes(decodeURIComponent(pair.split('=')[0])))
229
- .join('&');
230
-
231
- if (newHash) {
232
- window.location.hash = newHash;
233
- } else {
234
- const { origin, pathname, search } = window.location;
235
-
236
- window.history.replaceState(null, document.title, `${origin}${pathname}${search}`);
237
- }
238
- }
239
-
240
- function isValidState(state: string): boolean {
241
- const key = `${storagePrefix}${statePrefix}${state}`;
242
- const found = window.localStorage.getItem(key) != null;
243
-
244
- if (found) {
245
- window.localStorage.removeItem(key);
246
- }
247
-
248
- return found;
249
- }
250
-
251
- function anchorContainsError(): boolean {
252
- return 'error' in getAnchorParams();
253
- }
71
+ // eslint-disable-next-line @typescript-eslint/naming-convention
72
+ type AuthHeaders = { Authorization: string } | { 'X-XSRF-Token': string } | undefined;
254
73
 
255
- function generateState(): string {
256
- // @ts-expect-error TS2365
257
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
258
- const state = (([1e7] + -1e3 + -4e3 + -8e3 + -1e11) as string).replace(
259
- /[018]/g, // @ts-expect-error TS2362
260
- c => (c ^ ((Math.random() * 256) & (0x0f >>> (c >>> 2)))).toString(16),
261
- );
262
- const key = `${storagePrefix}${statePrefix}${state}`;
74
+ export function getAuthorizationHeaders(method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'): AuthHeaders {
75
+ const cookie = !token && method !== 'GET' && method !== 'HEAD'
76
+ ? document.cookie.split(/ *; */).find(pair => pair.startsWith('XSRF-TOKEN'))?.split('=')[1]
77
+ : undefined;
263
78
 
264
- window.localStorage.setItem(key, `${Date.now()}`);
265
-
266
- return state;
267
- }
268
-
269
- class OAuthError extends Error {
270
- error: string;
271
-
272
- constructor(message: string, error: unknown) {
273
- super(message);
274
-
275
- this.error = String(error);
276
- }
277
-
278
- toString(): string {
279
- let error = this.error;
280
-
281
- if (error.includes('_')) {
282
- error = error.replace('_', ' ').replace(/^./, c => c.toUpperCase());
283
- }
284
-
285
- return this.message ? `${error}: ${this.message}` : error;
286
- }
287
- }
288
-
289
- function getError(): OAuthError {
290
- const params = getAnchorParams();
291
-
292
- return params.message
293
- ? new OAuthError(params.message as string, params.error)
294
- : new OAuthError(titleCase(params.error), params.error);
295
- }
296
-
297
- /**
298
- * Our goal is to support even obsolete platforms (ES2017+ / Node.js 8.10+).
299
- * This is a small polyfill for possibly missing method used in our codebase.
300
- */
301
- if (!Object.fromEntries) { // eslint-disable-next-line arrow-body-style
302
- Object.fromEntries = <T = never>(entries: Iterable<readonly [string | number, T]>): { [k: string]: T } => {
303
- return Array.from(entries).reduce<{ [k: string]: T }>(
304
- (object, entry) => {
305
- if (!Array.isArray(entry)) {
306
- throw new TypeError(`Iterator value ${entry as unknown as string} is not an entry object.`);
307
- }
308
- object[`${entry[0]}`] = entry[1];
309
-
310
- return object;
311
- }, {}
312
- );
313
- };
79
+ return token
80
+ ? { Authorization: token.toString() }
81
+ : cookie
82
+ ? { 'X-XSRF-Token': decodeURIComponent(cookie) }
83
+ : undefined;
314
84
  }