@mapcreator/api 0.0.0-saga.6 → 0.0.0-saga.8
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.
Potentially problematic release.
This version of @mapcreator/api might be problematic. Click here for more details.
- package/cjs/api/choropleth.d.ts +97 -8
- package/cjs/api/choropleth.d.ts.map +1 -1
- package/cjs/api/choropleth.js +33 -15
- package/cjs/api/choropleth.js.map +1 -1
- package/cjs/api/font.d.ts.map +1 -1
- package/cjs/api/font.js.map +1 -1
- package/cjs/api/geocode.d.ts +10 -0
- package/cjs/api/geocode.d.ts.map +1 -0
- package/cjs/api/geocode.js +9 -0
- package/cjs/api/geocode.js.map +1 -0
- package/cjs/api/insetMap.d.ts.map +1 -1
- package/cjs/api/insetMap.js +3 -5
- package/cjs/api/insetMap.js.map +1 -1
- package/cjs/api/jobRevision.d.ts +1 -1
- package/cjs/api/jobRevision.d.ts.map +1 -1
- package/cjs/api/jobRevision.js +10 -16
- package/cjs/api/jobRevision.js.map +1 -1
- package/cjs/api/mapstyleSet.d.ts +2 -1
- package/cjs/api/mapstyleSet.d.ts.map +1 -1
- package/cjs/api/mapstyleSet.js +9 -0
- package/cjs/api/mapstyleSet.js.map +1 -1
- package/cjs/api/organisation.d.ts +2 -2
- package/cjs/api/organisation.d.ts.map +1 -1
- package/cjs/api/organisation.js +4 -4
- package/cjs/api/organisation.js.map +1 -1
- package/cjs/api/user.d.ts +2 -1
- package/cjs/api/user.d.ts.map +1 -1
- package/cjs/api/user.js +2 -3
- package/cjs/api/user.js.map +1 -1
- package/cjs/index.d.ts +1 -0
- package/cjs/index.d.ts.map +1 -1
- package/cjs/index.js +1 -0
- package/cjs/index.js.map +1 -1
- package/cjs/oauth.d.ts +10 -6
- package/cjs/oauth.d.ts.map +1 -1
- package/cjs/oauth.js +30 -199
- package/cjs/oauth.js.map +1 -1
- package/cjs/utils.d.ts.map +1 -1
- package/cjs/utils.js +3 -3
- package/cjs/utils.js.map +1 -1
- package/esm/api/choropleth.d.ts +97 -8
- package/esm/api/choropleth.d.ts.map +1 -1
- package/esm/api/choropleth.js +28 -13
- package/esm/api/choropleth.js.map +1 -1
- package/esm/api/font.d.ts.map +1 -1
- package/esm/api/font.js +1 -1
- package/esm/api/font.js.map +1 -1
- package/esm/api/geocode.d.ts +10 -0
- package/esm/api/geocode.d.ts.map +1 -0
- package/esm/api/geocode.js +6 -0
- package/esm/api/geocode.js.map +1 -0
- package/esm/api/insetMap.d.ts.map +1 -1
- package/esm/api/insetMap.js +4 -6
- package/esm/api/insetMap.js.map +1 -1
- package/esm/api/jobRevision.d.ts +1 -1
- package/esm/api/jobRevision.d.ts.map +1 -1
- package/esm/api/jobRevision.js +10 -16
- package/esm/api/jobRevision.js.map +1 -1
- package/esm/api/mapstyleSet.d.ts +2 -1
- package/esm/api/mapstyleSet.d.ts.map +1 -1
- package/esm/api/mapstyleSet.js +8 -0
- package/esm/api/mapstyleSet.js.map +1 -1
- package/esm/api/organisation.d.ts +2 -2
- package/esm/api/organisation.d.ts.map +1 -1
- package/esm/api/organisation.js +4 -4
- package/esm/api/organisation.js.map +1 -1
- package/esm/api/user.d.ts +2 -1
- package/esm/api/user.d.ts.map +1 -1
- package/esm/api/user.js +2 -3
- package/esm/api/user.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/oauth.d.ts +10 -6
- package/esm/oauth.d.ts.map +1 -1
- package/esm/oauth.js +29 -198
- package/esm/oauth.js.map +1 -1
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +4 -4
- package/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/api/choropleth.ts +177 -46
- package/src/api/font.ts +9 -1
- package/src/api/geocode.ts +17 -0
- package/src/api/insetMap.ts +4 -5
- package/src/api/jobRevision.ts +9 -7
- package/src/api/mapstyleSet.ts +23 -1
- package/src/api/organisation.ts +5 -4
- package/src/api/user.ts +3 -3
- package/src/index.ts +1 -0
- package/src/oauth.ts +31 -255
- package/src/utils.ts +4 -4
package/src/api/font.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
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;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ApiError, type ApiSuccess, request } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
export type GeocodeResult = {
|
|
4
|
+
index: number;
|
|
5
|
+
lat: number | undefined;
|
|
6
|
+
lng: number | undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export async function bulkGeocode(addresses: Array<{ index: number; address: string }>): Promise<GeocodeResult[]> {
|
|
10
|
+
const pathname = `/v1/geocode`;
|
|
11
|
+
|
|
12
|
+
type GeocodeResultArray = {
|
|
13
|
+
data: GeocodeResult[];
|
|
14
|
+
} & Omit<ApiSuccess, 'data'> | ApiError;
|
|
15
|
+
|
|
16
|
+
return request<GeocodeResultArray, GeocodeResult>(pathname, { items: addresses });
|
|
17
|
+
}
|
package/src/api/insetMap.ts
CHANGED
|
@@ -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 =
|
|
67
|
-
const response = await fetch(href, { headers
|
|
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(() => {
|
package/src/api/jobRevision.ts
CHANGED
|
@@ -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 {
|
|
@@ -70,6 +70,7 @@ export async function createJobRevision(
|
|
|
70
70
|
layers: number[],
|
|
71
71
|
output: FileFormat,
|
|
72
72
|
jobObject: Record<string, unknown>,
|
|
73
|
+
skipValidation?: boolean
|
|
73
74
|
): Promise<JobRevision> {
|
|
74
75
|
const pathname = `/v1/jobs/${jobId}/revisions`;
|
|
75
76
|
const path = `${pathname}?${deletedNoneParam}`;
|
|
@@ -79,6 +80,7 @@ export async function createJobRevision(
|
|
|
79
80
|
object: JSON.stringify(jobObject),
|
|
80
81
|
output,
|
|
81
82
|
layers,
|
|
83
|
+
...skipValidation && { skip_validation: skipValidation },
|
|
82
84
|
};
|
|
83
85
|
const options = { revivers: jobRevisionRevivers };
|
|
84
86
|
|
|
@@ -221,10 +223,9 @@ export async function getJobRevisionOutput(jobId: number): Promise<JobRevisionOu
|
|
|
221
223
|
await createJobRevisionBuild(jobId);
|
|
222
224
|
|
|
223
225
|
const href = `${apiHost}/v1/jobs/${jobId}/revisions/${lastJobRevision}/result/output`;
|
|
224
|
-
const headers =
|
|
225
|
-
const response = await fetch(href, { headers
|
|
226
|
-
throw new NetworkError(error?.message ?? error);
|
|
227
|
-
});
|
|
226
|
+
const headers = getAuthorizationHeaders('GET');
|
|
227
|
+
const response = await fetch(href, { headers, ...!token && { credentials: 'include' } })
|
|
228
|
+
.catch((error: Error) => { throw new NetworkError(error?.message ?? error) });
|
|
228
229
|
|
|
229
230
|
if (response.ok) {
|
|
230
231
|
const blob = await response.blob().catch(() => {
|
|
@@ -271,9 +272,10 @@ export async function getJobRevisionOutput(jobId: number): Promise<JobRevisionOu
|
|
|
271
272
|
}
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
async function createJobRevisionBuild(jobId: number): Promise<string> {
|
|
275
|
+
async function createJobRevisionBuild(jobId: number, skipValidation?: boolean): Promise<string> {
|
|
275
276
|
const pathname = `/v1/jobs/${jobId}/revisions/${lastJobRevision}/build`;
|
|
276
277
|
const path = `${pathname}?${deletedNoneParam}`;
|
|
278
|
+
const body = skipValidation ? { skip_validation: skipValidation } : null;
|
|
277
279
|
|
|
278
|
-
return request<ApiCommon, string>(path,
|
|
280
|
+
return request<ApiCommon, string>(path, body, null, { method: 'POST' });
|
|
279
281
|
}
|
package/src/api/mapstyleSet.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type ApiCommonData,
|
|
3
|
+
type ApiError,
|
|
4
|
+
type ApiSuccess,
|
|
5
|
+
type Flatten,
|
|
6
|
+
type Revivers,
|
|
7
|
+
defaultListHeader,
|
|
8
|
+
getSearchParams,
|
|
9
|
+
request,
|
|
10
|
+
} from '../utils.js';
|
|
2
11
|
|
|
3
12
|
export type MapstyleSet = {
|
|
4
13
|
id: number;
|
|
@@ -46,3 +55,16 @@ export const mapstyleSetRevivers: Revivers<ApiMapstyleSet, MapstyleSet> = {
|
|
|
46
55
|
order: undefined,
|
|
47
56
|
custom_settings: undefined,
|
|
48
57
|
};
|
|
58
|
+
|
|
59
|
+
export async function listMapstyleSets(jobTypeId?: number): Promise<MapstyleSet[]> {
|
|
60
|
+
const pathname = `/v1/mapstyles/sets`;
|
|
61
|
+
const query = jobTypeId !== undefined ? getSearchParams({ search: { job_type_id: jobTypeId } }) : null;
|
|
62
|
+
const path = query ? `${pathname}?${query}` : pathname;
|
|
63
|
+
const options = { revivers: mapstyleSetRevivers };
|
|
64
|
+
|
|
65
|
+
type ApiMapstyleSetArray = {
|
|
66
|
+
data: ApiMapstyleSetData[];
|
|
67
|
+
} & Omit<ApiSuccess, 'data'> | ApiError;
|
|
68
|
+
|
|
69
|
+
return request<ApiMapstyleSetArray, MapstyleSet>(path, null, defaultListHeader, options);
|
|
70
|
+
}
|
package/src/api/organisation.ts
CHANGED
|
@@ -63,8 +63,10 @@ export const organisationRevivers: Revivers<ApiOrganisation, Organisation> = {
|
|
|
63
63
|
exportTemplates: (data: ApiOrganisationData) => data.template_settings ?? '{}',
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
export async function getOrganisation(
|
|
67
|
-
|
|
66
|
+
export async function getOrganisation(
|
|
67
|
+
organisationId: number | typeof myOrganisations = myOrganisations,
|
|
68
|
+
): Promise<Organisation> {
|
|
69
|
+
const path = `/v1/organisations/${organisationId}`;
|
|
68
70
|
const options = { revivers: organisationRevivers };
|
|
69
71
|
|
|
70
72
|
return request<ApiOrganisation, Organisation>(path, null, null, options);
|
|
@@ -73,11 +75,10 @@ export async function getOrganisation(): Promise<Organisation> {
|
|
|
73
75
|
export async function listOrganisationJobs(
|
|
74
76
|
organisationId: number | typeof myOrganisations,
|
|
75
77
|
title: string,
|
|
76
|
-
shared: boolean,
|
|
77
78
|
page: number,
|
|
78
79
|
options?: Record<string, unknown>,
|
|
79
80
|
): Promise<JobSearchResult> {
|
|
80
|
-
const search = { ...options
|
|
81
|
+
const search = { ...options };
|
|
81
82
|
|
|
82
83
|
return listJobs(`/v1/organisations/${organisationId}/jobs`, title, page, search);
|
|
83
84
|
}
|
package/src/api/user.ts
CHANGED
|
@@ -40,6 +40,7 @@ type UnitType = 'metric' | 'imperial' | null;
|
|
|
40
40
|
|
|
41
41
|
export type User = {
|
|
42
42
|
id: number;
|
|
43
|
+
organisationId: number;
|
|
43
44
|
languageCode: string;
|
|
44
45
|
interfaceLanguage: string;
|
|
45
46
|
videoNotifications: boolean;
|
|
@@ -121,7 +122,6 @@ export type ApiJobFolder = {
|
|
|
121
122
|
export type ApiJobFolderData = Flatten<Exclude<ApiJobFolder, ApiError>['data']>;
|
|
122
123
|
|
|
123
124
|
export const userRevivers: Revivers<ApiUser, User> = {
|
|
124
|
-
organisation_id: undefined,
|
|
125
125
|
hide_messages: undefined,
|
|
126
126
|
phone: undefined,
|
|
127
127
|
profession: undefined,
|
|
@@ -159,8 +159,8 @@ type UpdateUserParams = {
|
|
|
159
159
|
email?: string;
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
-
export async function getUser(): Promise<User> {
|
|
163
|
-
const path = `/v1/users/${
|
|
162
|
+
export async function getUser(userId: number | typeof myUser = myUser): Promise<User> {
|
|
163
|
+
const path = `/v1/users/${userId}`;
|
|
164
164
|
const options = { revivers: userRevivers };
|
|
165
165
|
|
|
166
166
|
return request<ApiUser, User>(path, null, null, options);
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './api/dimensionSet.js';
|
|
|
5
5
|
export * from './api/feature.js';
|
|
6
6
|
export * from './api/font.js';
|
|
7
7
|
export * from './api/fontFamily.js';
|
|
8
|
+
export * from './api/geocode.js';
|
|
8
9
|
export * from './api/highlight.js';
|
|
9
10
|
export * from './api/insetMap.js';
|
|
10
11
|
export * from './api/job.js';
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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:
|
|
35
|
+
type: parts[0].toLowerCase().replace(/\b\w/g, c => c.toUpperCase()),
|
|
44
36
|
token: parts[1],
|
|
45
|
-
expires:
|
|
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,45 @@ 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,
|
|
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
|
-
|
|
133
|
-
const href = sessionStorage.getItem('redirect-url');
|
|
56
|
+
const href = sessionStorage.getItem('redirect-url');
|
|
134
57
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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<
|
|
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(
|
|
67
|
+
window.location.assign(`${apiHost}/login?${new URLSearchParams({ redirect_uri: callbackUrl })}`);
|
|
153
68
|
});
|
|
154
69
|
}
|
|
155
70
|
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
}
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
72
|
+
type AuthHeaders = { Authorization: string } | { 'X-XSRF-Token': string } | undefined;
|
|
176
73
|
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (key?.startsWith(storagePrefix)) {
|
|
182
|
-
window.localStorage.removeItem(key);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
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;
|
|
185
78
|
|
|
186
|
-
token
|
|
79
|
+
return token
|
|
80
|
+
? { Authorization: token.toString() }
|
|
81
|
+
: cookie
|
|
82
|
+
? { 'X-XSRF-Token': decodeURIComponent(cookie) }
|
|
83
|
+
: undefined;
|
|
187
84
|
}
|
|
188
85
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
redirect_uri: callbackUrl,
|
|
193
|
-
response_type: 'token',
|
|
194
|
-
scope: oauthScopes.join(' '),
|
|
195
|
-
state: generateState(),
|
|
86
|
+
export async function logout(): Promise<never> {
|
|
87
|
+
return new Promise(() => {
|
|
88
|
+
window.location.assign(`${apiHost}/logout?${new URLSearchParams({ redirect_uri: callbackUrl })}`);
|
|
196
89
|
});
|
|
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
|
-
}
|
|
254
|
-
|
|
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}`;
|
|
263
|
-
|
|
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
|
-
};
|
|
314
90
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { apiHost, authenticate, getAuthorizationHeaders, token } from './oauth.js';
|
|
1
2
|
import type { CamelCasedProperties, SnakeCasedProperties } from 'type-fest';
|
|
2
|
-
import { apiHost, authenticate, token } from './oauth.js';
|
|
3
3
|
|
|
4
4
|
export type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
|
|
5
5
|
|
|
@@ -233,7 +233,6 @@ function getRequestInit<I extends ApiCommon, O extends Record<string, unknown>>(
|
|
|
233
233
|
extraHeaders?: Record<string, string> | null,
|
|
234
234
|
extraOptions?: ExtraOptions<I, O>,
|
|
235
235
|
): RequestInit {
|
|
236
|
-
const authorization = token ? { Authorization: token.toString() } : null;
|
|
237
236
|
let contentType = null as { 'Content-Type': string } | null;
|
|
238
237
|
|
|
239
238
|
if (body !== undefined) {
|
|
@@ -259,10 +258,11 @@ function getRequestInit<I extends ApiCommon, O extends Record<string, unknown>>(
|
|
|
259
258
|
}
|
|
260
259
|
}
|
|
261
260
|
|
|
262
|
-
const headers = { Accept: 'application/json', ...authorization, ...contentType, ...extraHeaders };
|
|
263
261
|
const method = extraOptions?.method ?? (body != null ? 'POST' : 'GET'); // don't touch `!=` please
|
|
262
|
+
const authorization = getAuthorizationHeaders(method);
|
|
263
|
+
const headers = { Accept: 'application/json', ...authorization, ...contentType, ...extraHeaders };
|
|
264
264
|
|
|
265
|
-
return { body, headers, method } as RequestInit;
|
|
265
|
+
return { body, headers, method, ...!token && { credentials: 'include' } } as RequestInit;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
interface Context<I extends ApiCommon, O extends Record<string, unknown>> {
|