@communecter/cocolight-api-client 1.0.51 → 1.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/123.cocolight-api-client.browser.js +1 -1
- package/dist/123.cocolight-api-client.cjs +1 -1
- package/dist/774.cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.browser.js +1 -1
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
- package/package.json +15 -9
- package/src/Api.js +41 -23
- package/src/ApiClient.js +33 -18
- package/src/api/EndpointApi.js +95 -95
- package/src/error.js +63 -10
- package/src/index.js +65 -2
- package/src/utils/FileStorageStrategy.node.js +28 -2
- package/src/utils/MultiServerFileStorageStrategy.node.js +24 -2
- package/src/utils/MultiServerTokenStorageStrategy.js +84 -27
- package/src/utils/TokenStorage.js +79 -19
- package/src/utils/createDefaultMultiServerTokenStorageStrategy.js +33 -27
- package/src/utils/createDefaultTokenStorageStrategy.js +29 -23
- package/types/Api.d.ts +131 -0
- package/types/ApiClient.d.ts +377 -0
- package/types/EJSONType.d.ts +27 -0
- package/types/api/Badge.d.ts +24 -0
- package/types/api/BaseEntity.d.ts +1017 -0
- package/types/api/EndpointApi.d.ts +933 -0
- package/types/api/EntityRegistry.d.ts +22 -0
- package/types/api/Event.d.ts +38 -0
- package/types/api/News.d.ts +45 -0
- package/types/api/Organization.d.ts +87 -0
- package/types/api/Poi.d.ts +25 -0
- package/types/api/Project.d.ts +81 -0
- package/types/api/User.d.ts +203 -0
- package/types/api/UserApi.d.ts +13 -0
- package/types/endpoints.module.d.ts +14852 -0
- package/types/error.d.ts +80 -0
- package/types/index.d.ts +52 -0
- package/types/mixin/UserMixin.d.ts +1 -0
- package/types/utils/FileOfflineStorageStrategy.node.d.ts +10 -0
- package/types/utils/FileStorageStrategy.node.d.ts +25 -0
- package/types/utils/MultiServerFileStorageStrategy.node.d.ts +22 -0
- package/types/utils/MultiServerTokenStorageStrategy.d.ts +65 -0
- package/types/utils/OfflineClientManager.d.ts +94 -0
- package/types/utils/OfflineQueueStorageStrategy.d.ts +13 -0
- package/types/utils/TokenStorage.d.ts +76 -0
- package/types/utils/createDefaultMultiServerTokenStorageStrategy.d.ts +11 -0
- package/types/utils/createDefaultOfflineStrategy.d.ts +3 -0
- package/types/utils/createDefaultTokenStorageStrategy.d.ts +12 -0
- package/types/utils/reactive.d.ts +60 -0
- package/types/utils/stream-utils.node.d.ts +2 -0
- /package/{src → types}/api/EndpointApi.types.d.ts +0 -0
package/src/error.js
CHANGED
|
@@ -1,68 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Record<string, any>} ApiErrorDetails
|
|
3
|
+
*/
|
|
1
4
|
|
|
2
5
|
/**
|
|
3
6
|
* Classe de base pour toutes les erreurs de l'API.
|
|
7
|
+
* @extends Error
|
|
4
8
|
*/
|
|
5
9
|
export class ApiError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} message - Message d'erreur.
|
|
12
|
+
* @param {number} status - Code HTTP (ex: 400, 401, 500, 503).
|
|
13
|
+
* @param {ApiErrorDetails} [details] - Détails supplémentaires.
|
|
14
|
+
*/
|
|
6
15
|
constructor(message, status, details) {
|
|
7
16
|
super(message);
|
|
17
|
+
/** @type {string} */
|
|
8
18
|
this.name = "ApiError";
|
|
19
|
+
/** @type {number} */
|
|
9
20
|
this.status = status;
|
|
21
|
+
/** @type {ApiErrorDetails|undefined} */
|
|
10
22
|
this.details = details;
|
|
11
23
|
}
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
/**
|
|
15
|
-
* Erreur levée par le client API lorsqu'un appel échoue.
|
|
27
|
+
* Erreur levée par le client API lorsqu'un appel échoue (réseau, config, etc.).
|
|
28
|
+
* @extends ApiError
|
|
16
29
|
*/
|
|
17
30
|
export class ApiClientError extends ApiError {
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} message - Message d'erreur.
|
|
33
|
+
* @param {number} status - Code HTTP.
|
|
34
|
+
* @param {ApiErrorDetails} [details] - Détails supplémentaires.
|
|
35
|
+
*/
|
|
18
36
|
constructor(message, status, details) {
|
|
19
37
|
super(message, status, details);
|
|
38
|
+
/** @type {string} */
|
|
20
39
|
this.name = "ApiClientError";
|
|
21
40
|
}
|
|
22
41
|
}
|
|
23
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Erreur de validation AJV (schemas de requête/réponse).
|
|
45
|
+
* @extends Error
|
|
46
|
+
*/
|
|
24
47
|
export class ApiValidationError extends Error {
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} message - Message d'erreur.
|
|
50
|
+
* @param {number} status - Code HTTP logique (souvent 400).
|
|
51
|
+
* @param {string[]} messages - Messages humains dérivés des erreurs AJV.
|
|
52
|
+
* @param {any} [details=null] - Erreurs AJV brutes ou métadonnées.
|
|
53
|
+
*/
|
|
25
54
|
constructor(message, status, messages, details = null) {
|
|
26
55
|
super(message);
|
|
56
|
+
/** @type {string} */
|
|
27
57
|
this.name = "ApiValidationError";
|
|
58
|
+
/** @type {number} */
|
|
28
59
|
this.status = status;
|
|
60
|
+
/** @type {string[]} */
|
|
29
61
|
this.messages = messages;
|
|
62
|
+
/** @type {any} */
|
|
30
63
|
this.details = details;
|
|
31
64
|
}
|
|
32
65
|
}
|
|
33
66
|
|
|
34
|
-
|
|
35
67
|
/**
|
|
36
|
-
*
|
|
68
|
+
* Levée lorsqu'on bloque l'accès à l'API (circuit breaker).
|
|
69
|
+
* @extends ApiError
|
|
37
70
|
*/
|
|
38
71
|
export class CircuitBreakerError extends ApiError {
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} message - Message d'erreur.
|
|
74
|
+
*/
|
|
39
75
|
constructor(message) {
|
|
40
76
|
super(message, 503);
|
|
77
|
+
/** @type {string} */
|
|
41
78
|
this.name = "CircuitBreakerError";
|
|
42
79
|
}
|
|
43
80
|
}
|
|
44
|
-
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Erreur "métier" retour de l'API (ex: result=false) même si HTTP=200.
|
|
84
|
+
* @extends Error
|
|
85
|
+
*/
|
|
45
86
|
export class ApiResponseError extends Error {
|
|
46
87
|
/**
|
|
47
|
-
* Crée une instance d'ApiResponseError.
|
|
48
|
-
*
|
|
49
88
|
* @param {string} message - Message d'erreur.
|
|
50
|
-
* @param {number} status -
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {any} details -
|
|
89
|
+
* @param {number} status - Code HTTP (peut être 200 pour erreurs logiques).
|
|
90
|
+
* @param {object} responseData - Payload renvoyé par l'API.
|
|
91
|
+
* @param {any} [details=null] - Infos supplémentaires (optionnel).
|
|
53
92
|
*/
|
|
54
93
|
constructor(message, status, responseData, details = null) {
|
|
55
94
|
super(message);
|
|
95
|
+
/** @type {string} */
|
|
56
96
|
this.name = "ApiResponseError";
|
|
97
|
+
/** @type {number} */
|
|
57
98
|
this.status = status;
|
|
99
|
+
/** @type {object} */
|
|
58
100
|
this.responseData = responseData;
|
|
101
|
+
/** @type {any} */
|
|
59
102
|
this.details = details;
|
|
60
103
|
}
|
|
61
104
|
}
|
|
62
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Erreur d'authentification (ex: 401/403).
|
|
108
|
+
* @extends ApiError
|
|
109
|
+
*/
|
|
63
110
|
export class ApiAuthenticationError extends ApiError {
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} message - Message d'erreur.
|
|
113
|
+
* @param {number} status - Code HTTP (401/403).
|
|
114
|
+
* @param {ApiErrorDetails} [details] - Détails supplémentaires.
|
|
115
|
+
*/
|
|
64
116
|
constructor(message, status, details) {
|
|
65
117
|
super(message, status, details);
|
|
118
|
+
/** @type {string} */
|
|
66
119
|
this.name = "ApiAuthenticationError";
|
|
67
120
|
}
|
|
68
|
-
}
|
|
121
|
+
}
|
package/src/index.js
CHANGED
|
@@ -9,26 +9,89 @@ import OfflineClientManager from "./utils/OfflineClientManager.js";
|
|
|
9
9
|
import * as reactive from "./utils/reactive.js";
|
|
10
10
|
import { TokenStorageStrategy } from "./utils/TokenStorage.js";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import("./Api.js").default} Api
|
|
14
|
+
* @typedef {import("./ApiClient.js").default} ApiClient
|
|
15
|
+
* @typedef {import("./error.js")} error
|
|
16
|
+
* @typedef {import("./utils/reactive.js")} reactive
|
|
17
|
+
* @typedef {import("./utils/TokenStorage.js").TokenStorageStrategy} TokenStorageStrategy
|
|
18
|
+
* @typedef {import("./utils/MultiServerTokenStorageStrategy.js").MultiServerTokenStorageStrategy} MultiServerTokenStorageStrategy
|
|
19
|
+
* @typedef {import("./utils/OfflineClientManager.js").default} OfflineClientManager
|
|
20
|
+
* @typedef {import("./api/User.js").User} User
|
|
21
|
+
* @typedef {import("./api/Organization.js").Organization} Organization
|
|
22
|
+
* @typedef {import("./api/Project.js").Project} Project
|
|
23
|
+
* @typedef {import("./api/Event.js").Event} Event
|
|
24
|
+
* @typedef {import("./api/Poi.js").Poi} Poi
|
|
25
|
+
* @typedef {import("./api/Badge.js").Badge} Badge
|
|
26
|
+
* @typedef {import("./api/News.js").News} News
|
|
27
|
+
* @typedef {import("./api/EndpointApi.js").default} EndpointApi
|
|
28
|
+
* @typedef {import("./api/UserApi.js").UserApi} UserApi
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Client API complet de Cocolight.
|
|
33
|
+
*/
|
|
34
|
+
const cocolightApiClient = {
|
|
35
|
+
/** @type {ApiClient} */
|
|
13
36
|
ApiClient,
|
|
37
|
+
|
|
38
|
+
/** @type {Api} */
|
|
14
39
|
Api,
|
|
40
|
+
|
|
41
|
+
/** @type {typeof import("./error.js")} */
|
|
15
42
|
error,
|
|
16
43
|
|
|
17
44
|
// Accès aux primitives réactives via .reactive
|
|
45
|
+
/** @type {typeof import("./utils/reactive.js")} */
|
|
18
46
|
reactive,
|
|
19
47
|
|
|
20
48
|
// Accès direct à chaque primitive (reactive, effect, watch...) en racine
|
|
21
|
-
|
|
49
|
+
/** @type {typeof import("./utils/reactive.js")["effect"]} */
|
|
50
|
+
effect: reactive.effect,
|
|
51
|
+
|
|
52
|
+
/** @type {typeof import("./utils/reactive.js")["batch"]} */
|
|
53
|
+
batch: reactive.batch,
|
|
54
|
+
|
|
55
|
+
/** @type {typeof import("./utils/reactive.js")["isSignal"]} */
|
|
56
|
+
isSignal: reactive.isSignal,
|
|
57
|
+
|
|
58
|
+
/** @type {typeof import("./utils/reactive.js")["reactive"]} */
|
|
59
|
+
reactiveObject: reactive.reactive, // évite le conflit de nom avec la clé "reactive" ci-dessus
|
|
60
|
+
|
|
61
|
+
/** @type {typeof import("./utils/reactive.js")["isReactive"]} */
|
|
62
|
+
isReactive: reactive.isReactive,
|
|
63
|
+
|
|
64
|
+
/** @type {typeof import("./utils/reactive.js")["computed"]} */
|
|
65
|
+
computed: reactive.computed,
|
|
66
|
+
|
|
67
|
+
/** @type {typeof import("./utils/reactive.js")["watch"]} */
|
|
68
|
+
watch: reactive.watch,
|
|
69
|
+
|
|
70
|
+
/** @type {typeof import("./utils/reactive.js")["getSignals"]} */
|
|
71
|
+
getSignals: reactive.getSignals,
|
|
72
|
+
|
|
73
|
+
/** @type {typeof import("./utils/reactive.js")["subscribeTo"]} */
|
|
74
|
+
subscribeTo: reactive.subscribeTo,
|
|
75
|
+
|
|
22
76
|
|
|
23
77
|
tokenStorageStrategy: {
|
|
78
|
+
/** @type {() => import("./utils/TokenStorage.js").TokenStorageStrategy} */
|
|
24
79
|
createDefaultTokenStorageStrategy,
|
|
80
|
+
/** @type {TokenStorageStrategy} */
|
|
25
81
|
TokenStorageStrategy,
|
|
82
|
+
/** @type {() => MultiServerTokenStorageStrategy} */
|
|
26
83
|
createDefaultMultiServerTokenStorageStrategy,
|
|
84
|
+
/** @type {MultiServerTokenStorageStrategy} */
|
|
27
85
|
MultiServerTokenStorageStrategy
|
|
28
86
|
},
|
|
87
|
+
|
|
29
88
|
helper: {
|
|
89
|
+
/** @type {typeof fromEntityJSON} */
|
|
30
90
|
fromEntityJSON
|
|
31
91
|
},
|
|
32
92
|
|
|
93
|
+
/** @type {OfflineClientManager} */
|
|
33
94
|
OfflineClientManager
|
|
34
95
|
};
|
|
96
|
+
|
|
97
|
+
export default cocolightApiClient;
|
|
@@ -1,59 +1,85 @@
|
|
|
1
|
-
// src/utils/FileStorageStrategy.node.js
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import os from "os";
|
|
4
3
|
import path from "path";
|
|
5
4
|
|
|
6
5
|
import { TokenStorageStrategy } from "./TokenStorage.js";
|
|
7
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Stratégie de stockage des tokens basée sur un fichier (Node uniquement).
|
|
9
|
+
* @implements {TokenStorageStrategy}
|
|
10
|
+
*/
|
|
8
11
|
export class FileStorageStrategy extends TokenStorageStrategy {
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} [filename="tokens.json"]
|
|
14
|
+
* @param {string} [dir=path.join(os.homedir(), ".config", "cocolight")]
|
|
15
|
+
*/
|
|
9
16
|
constructor(filename = "tokens.json", dir = path.join(os.homedir(), ".config", "cocolight")) {
|
|
10
17
|
super();
|
|
18
|
+
/** @type {string} */
|
|
11
19
|
this.dir = dir;
|
|
20
|
+
/** @type {string} */
|
|
12
21
|
this.filePath = path.join(dir, filename);
|
|
13
22
|
this._ensureDirectoryExists();
|
|
14
23
|
}
|
|
15
24
|
|
|
25
|
+
/** @private */
|
|
16
26
|
_ensureDirectoryExists() {
|
|
17
27
|
if (!fs.existsSync(this.dir)) {
|
|
18
28
|
fs.mkdirSync(this.dir, { recursive: true });
|
|
19
29
|
}
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
/** @private */
|
|
22
33
|
_readFile() {
|
|
23
34
|
if (!fs.existsSync(this.filePath)) return {};
|
|
24
35
|
try {
|
|
25
36
|
return JSON.parse(fs.readFileSync(this.filePath, "utf8"));
|
|
26
|
-
|
|
27
37
|
} catch (e) {
|
|
38
|
+
|
|
28
39
|
console.error("Error reading token file:", e);
|
|
29
40
|
return {};
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @private
|
|
46
|
+
* @param {Record<string, any>} data
|
|
47
|
+
*/
|
|
33
48
|
_writeFile(data) {
|
|
34
49
|
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf8");
|
|
35
50
|
}
|
|
36
51
|
|
|
52
|
+
/** @returns {string|null} */
|
|
37
53
|
getAccessToken() {
|
|
38
54
|
return this._readFile().accessToken || null;
|
|
39
55
|
}
|
|
40
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @param {string|null} token
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
41
61
|
setAccessToken(token) {
|
|
42
62
|
const data = this._readFile();
|
|
43
63
|
data.accessToken = token;
|
|
44
64
|
this._writeFile(data);
|
|
45
65
|
}
|
|
46
66
|
|
|
67
|
+
/** @returns {string|null} */
|
|
47
68
|
getRefreshToken() {
|
|
48
69
|
return this._readFile().refreshToken || null;
|
|
49
70
|
}
|
|
50
71
|
|
|
72
|
+
/**
|
|
73
|
+
* @param {string|null} token
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
51
76
|
setRefreshToken(token) {
|
|
52
77
|
const data = this._readFile();
|
|
53
78
|
data.refreshToken = token;
|
|
54
79
|
this._writeFile(data);
|
|
55
80
|
}
|
|
56
81
|
|
|
82
|
+
/** @returns {void} */
|
|
57
83
|
clear() {
|
|
58
84
|
this._writeFile({});
|
|
59
85
|
}
|
|
@@ -4,13 +4,25 @@ import path from "path";
|
|
|
4
4
|
|
|
5
5
|
import { MultiServerTokenStorageStrategy } from "./MultiServerTokenStorageStrategy.js";
|
|
6
6
|
|
|
7
|
+
/** @typedef {import("./MultiServerTokenStorageStrategy.js").MultiServerTokenStorageStrategy} MultiServerTokenStorageStrategy */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stockage fichier (Node uniquement) multi-serveurs.
|
|
11
|
+
* @implements {MultiServerTokenStorageStrategy}
|
|
12
|
+
*/
|
|
7
13
|
export class MultiServerFileStorageStrategy extends MultiServerTokenStorageStrategy {
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} [filename="tokens.json"]
|
|
16
|
+
* @param {string} [dir=path.join(os.homedir(), ".config", "cocolight")]
|
|
17
|
+
*/
|
|
8
18
|
constructor(filename = "tokens.json", dir = path.join(os.homedir(), ".config", "cocolight")) {
|
|
9
19
|
super();
|
|
20
|
+
/** @type {string} */
|
|
10
21
|
this.filePath = path.join(dir, filename);
|
|
11
22
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
12
23
|
}
|
|
13
24
|
|
|
25
|
+
/** @private */
|
|
14
26
|
_readFile() {
|
|
15
27
|
if (!fs.existsSync(this.filePath)) return {};
|
|
16
28
|
try {
|
|
@@ -20,15 +32,20 @@ export class MultiServerFileStorageStrategy extends MultiServerTokenStorageStrat
|
|
|
20
32
|
}
|
|
21
33
|
}
|
|
22
34
|
|
|
35
|
+
/** @private
|
|
36
|
+
* @param {Record<string, {accessToken?: string|null, refreshToken?: string|null}>} data
|
|
37
|
+
*/
|
|
23
38
|
_writeFile(data) {
|
|
24
39
|
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf8");
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
/** @returns {string|null} */
|
|
27
43
|
getAccessToken() {
|
|
28
44
|
this._ensureContext();
|
|
29
|
-
return this._readFile()[this._currentBaseURL]?.accessToken
|
|
45
|
+
return this._readFile()[this._currentBaseURL]?.accessToken ?? null;
|
|
30
46
|
}
|
|
31
47
|
|
|
48
|
+
/** @param {string|null} token */
|
|
32
49
|
setAccessToken(token) {
|
|
33
50
|
this._ensureContext();
|
|
34
51
|
const all = this._readFile();
|
|
@@ -36,11 +53,13 @@ export class MultiServerFileStorageStrategy extends MultiServerTokenStorageStrat
|
|
|
36
53
|
this._writeFile(all);
|
|
37
54
|
}
|
|
38
55
|
|
|
56
|
+
/** @returns {string|null} */
|
|
39
57
|
getRefreshToken() {
|
|
40
58
|
this._ensureContext();
|
|
41
|
-
return this._readFile()[this._currentBaseURL]?.refreshToken
|
|
59
|
+
return this._readFile()[this._currentBaseURL]?.refreshToken ?? null;
|
|
42
60
|
}
|
|
43
61
|
|
|
62
|
+
/** @param {string|null} token */
|
|
44
63
|
setRefreshToken(token) {
|
|
45
64
|
this._ensureContext();
|
|
46
65
|
const all = this._readFile();
|
|
@@ -48,6 +67,7 @@ export class MultiServerFileStorageStrategy extends MultiServerTokenStorageStrat
|
|
|
48
67
|
this._writeFile(all);
|
|
49
68
|
}
|
|
50
69
|
|
|
70
|
+
/** @returns {void} */
|
|
51
71
|
clear() {
|
|
52
72
|
this._ensureContext();
|
|
53
73
|
const all = this._readFile();
|
|
@@ -55,10 +75,12 @@ export class MultiServerFileStorageStrategy extends MultiServerTokenStorageStrat
|
|
|
55
75
|
this._writeFile(all);
|
|
56
76
|
}
|
|
57
77
|
|
|
78
|
+
/** @returns {string[]} */
|
|
58
79
|
getServers() {
|
|
59
80
|
return Object.keys(this._readFile());
|
|
60
81
|
}
|
|
61
82
|
|
|
83
|
+
/** @returns {Record<string, {accessToken?: string|null, refreshToken?: string|null}>} */
|
|
62
84
|
exportAll() {
|
|
63
85
|
return this._readFile();
|
|
64
86
|
}
|
|
@@ -1,131 +1,188 @@
|
|
|
1
|
-
// MultiServerTokenStorageStrategy.js
|
|
2
1
|
import { TokenStorageStrategy } from "./TokenStorage.js";
|
|
3
2
|
|
|
3
|
+
/** @typedef {import("./TokenStorage.js").TokenStorageStrategy} TokenStorageStrategy */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Stratégie de stockage multi-serveurs (abstraite).
|
|
7
|
+
* Permet de sélectionner un `baseURL` courant via `use()` puis d'opérer get/set/clear dessus.
|
|
8
|
+
* @abstract
|
|
9
|
+
* @extends TokenStorageStrategy
|
|
10
|
+
*/
|
|
4
11
|
export class MultiServerTokenStorageStrategy extends TokenStorageStrategy {
|
|
5
12
|
constructor() {
|
|
6
13
|
super();
|
|
14
|
+
/** @type {string|null} */
|
|
7
15
|
this._currentBaseURL = null;
|
|
8
16
|
}
|
|
9
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Sélectionne le serveur courant (normalise en supprimant les `/` finaux).
|
|
20
|
+
* @param {string} baseURL
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
10
23
|
use(baseURL) {
|
|
11
24
|
if (!baseURL) throw new Error("baseURL est requis");
|
|
12
25
|
this._currentBaseURL = baseURL.replace(/\/+$/, "");
|
|
13
26
|
}
|
|
14
27
|
|
|
28
|
+
/** @private */
|
|
15
29
|
_ensureContext() {
|
|
16
30
|
if (!this._currentBaseURL) throw new Error("baseURL non défini. Appelez use(baseURL)");
|
|
17
31
|
}
|
|
18
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Retourne la liste des serveurs connus.
|
|
35
|
+
* @abstract
|
|
36
|
+
* @returns {string[]}
|
|
37
|
+
*/
|
|
19
38
|
getServers() {
|
|
20
39
|
throw new Error("getServers() doit être implémenté");
|
|
21
40
|
}
|
|
22
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Exporte toutes les données stockées (par baseURL).
|
|
44
|
+
* @abstract
|
|
45
|
+
* @returns {Record<string, {accessToken?: string|null, refreshToken?: string|null}>}
|
|
46
|
+
*/
|
|
23
47
|
exportAll() {
|
|
24
48
|
throw new Error("exportAll() doit être implémenté");
|
|
25
49
|
}
|
|
26
50
|
}
|
|
27
51
|
|
|
28
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Stockage en mémoire (multi-serveurs).
|
|
54
|
+
* @implements {MultiServerTokenStorageStrategy}
|
|
55
|
+
*/
|
|
29
56
|
export class MultiServerMemoryStorageStrategy extends MultiServerTokenStorageStrategy {
|
|
30
57
|
constructor() {
|
|
31
58
|
super();
|
|
59
|
+
/** @type {Map<string, {accessToken?: string|null, refreshToken?: string|null}>} */
|
|
32
60
|
this._storage = new Map();
|
|
33
61
|
}
|
|
34
|
-
|
|
62
|
+
|
|
63
|
+
/** @returns {string|null} */
|
|
35
64
|
getAccessToken() {
|
|
36
65
|
this._ensureContext();
|
|
37
|
-
return this._storage.get(this._currentBaseURL)?.accessToken
|
|
66
|
+
return this._storage.get(this._currentBaseURL)?.accessToken ?? null;
|
|
38
67
|
}
|
|
39
|
-
|
|
68
|
+
|
|
69
|
+
/** @param {string|null} token */
|
|
40
70
|
setAccessToken(token) {
|
|
41
71
|
this._ensureContext();
|
|
42
72
|
const current = this._storage.get(this._currentBaseURL) || {};
|
|
43
73
|
this._storage.set(this._currentBaseURL, { ...current, accessToken: token });
|
|
44
74
|
}
|
|
45
|
-
|
|
75
|
+
|
|
76
|
+
/** @returns {string|null} */
|
|
46
77
|
getRefreshToken() {
|
|
47
78
|
this._ensureContext();
|
|
48
|
-
return this._storage.get(this._currentBaseURL)?.refreshToken
|
|
79
|
+
return this._storage.get(this._currentBaseURL)?.refreshToken ?? null;
|
|
49
80
|
}
|
|
50
|
-
|
|
81
|
+
|
|
82
|
+
/** @param {string|null} token */
|
|
51
83
|
setRefreshToken(token) {
|
|
52
84
|
this._ensureContext();
|
|
53
85
|
const current = this._storage.get(this._currentBaseURL) || {};
|
|
54
86
|
this._storage.set(this._currentBaseURL, { ...current, refreshToken: token });
|
|
55
87
|
}
|
|
56
|
-
|
|
88
|
+
|
|
89
|
+
/** @returns {void} */
|
|
57
90
|
clear() {
|
|
58
91
|
this._ensureContext();
|
|
59
92
|
this._storage.delete(this._currentBaseURL);
|
|
60
93
|
}
|
|
61
|
-
|
|
94
|
+
|
|
95
|
+
/** @returns {string[]} */
|
|
62
96
|
getServers() {
|
|
63
97
|
return [...this._storage.keys()];
|
|
64
98
|
}
|
|
65
|
-
|
|
99
|
+
|
|
100
|
+
/** @returns {Record<string, {accessToken?: string|null, refreshToken?: string|null}>} */
|
|
66
101
|
exportAll() {
|
|
67
102
|
return Object.fromEntries(this._storage.entries());
|
|
68
103
|
}
|
|
69
104
|
}
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Stockage navigateur (localStorage) multi-serveurs.
|
|
108
|
+
* @implements {MultiServerTokenStorageStrategy}
|
|
109
|
+
*/
|
|
72
110
|
export class MultiServerLocalStorageStrategy extends MultiServerTokenStorageStrategy {
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} [prefix="cocolight"]
|
|
113
|
+
*/
|
|
73
114
|
constructor(prefix = "cocolight") {
|
|
74
115
|
super();
|
|
116
|
+
/** @type {string} */
|
|
75
117
|
this.prefix = prefix;
|
|
76
118
|
}
|
|
77
|
-
|
|
119
|
+
|
|
120
|
+
/** @private */
|
|
78
121
|
_key(baseURL) {
|
|
79
122
|
return `${this.prefix}_tokens_${baseURL}`;
|
|
80
123
|
}
|
|
81
|
-
|
|
124
|
+
|
|
125
|
+
/** @private */
|
|
82
126
|
_getData() {
|
|
83
127
|
this._ensureContext();
|
|
128
|
+
if (typeof localStorage === "undefined") return {};
|
|
84
129
|
const raw = localStorage.getItem(this._key(this._currentBaseURL));
|
|
85
130
|
return raw ? JSON.parse(raw) : {};
|
|
86
131
|
}
|
|
87
|
-
|
|
132
|
+
|
|
133
|
+
/** @private */
|
|
88
134
|
_setData(data) {
|
|
135
|
+
if (typeof localStorage === "undefined") return;
|
|
89
136
|
localStorage.setItem(this._key(this._currentBaseURL), JSON.stringify(data));
|
|
90
137
|
}
|
|
91
|
-
|
|
138
|
+
|
|
139
|
+
/** @returns {string|null} */
|
|
92
140
|
getAccessToken() {
|
|
93
|
-
return this._getData().accessToken
|
|
141
|
+
return this._getData().accessToken ?? null;
|
|
94
142
|
}
|
|
95
|
-
|
|
143
|
+
|
|
144
|
+
/** @param {string|null} token */
|
|
96
145
|
setAccessToken(token) {
|
|
97
146
|
const data = this._getData();
|
|
98
147
|
data.accessToken = token;
|
|
99
148
|
this._setData(data);
|
|
100
149
|
}
|
|
101
|
-
|
|
150
|
+
|
|
151
|
+
/** @returns {string|null} */
|
|
102
152
|
getRefreshToken() {
|
|
103
|
-
return this._getData().refreshToken
|
|
153
|
+
return this._getData().refreshToken ?? null;
|
|
104
154
|
}
|
|
105
|
-
|
|
155
|
+
|
|
156
|
+
/** @param {string|null} token */
|
|
106
157
|
setRefreshToken(token) {
|
|
107
158
|
const data = this._getData();
|
|
108
159
|
data.refreshToken = token;
|
|
109
160
|
this._setData(data);
|
|
110
161
|
}
|
|
111
|
-
|
|
162
|
+
|
|
163
|
+
/** @returns {void} */
|
|
112
164
|
clear() {
|
|
165
|
+
if (typeof localStorage === "undefined") return;
|
|
113
166
|
localStorage.removeItem(this._key(this._currentBaseURL));
|
|
114
167
|
}
|
|
115
|
-
|
|
168
|
+
|
|
169
|
+
/** @returns {string[]} */
|
|
116
170
|
getServers() {
|
|
171
|
+
if (typeof localStorage === "undefined") return [];
|
|
117
172
|
const keys = Object.keys(localStorage);
|
|
118
173
|
return keys
|
|
119
174
|
.filter(k => k.startsWith(`${this.prefix}_tokens_`))
|
|
120
175
|
.map(k => k.replace(`${this.prefix}_tokens_`, ""));
|
|
121
176
|
}
|
|
122
|
-
|
|
177
|
+
|
|
178
|
+
/** @returns {Record<string, {accessToken?: string|null, refreshToken?: string|null}>} */
|
|
123
179
|
exportAll() {
|
|
124
180
|
const result = {};
|
|
125
181
|
for (const baseURL of this.getServers()) {
|
|
126
|
-
const
|
|
182
|
+
const key = this._key(baseURL);
|
|
183
|
+
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
|
|
127
184
|
if (raw) result[baseURL] = JSON.parse(raw);
|
|
128
185
|
}
|
|
129
186
|
return result;
|
|
130
187
|
}
|
|
131
|
-
}
|
|
188
|
+
}
|