@openmrs/esm-dynamic-loading 9.0.3-pre.4533 → 9.0.3-pre.4537
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/.turbo/turbo-build.log +1 -1
- package/dist/dynamic-loading.d.ts.map +1 -1
- package/dist/dynamic-loading.js +4 -4
- package/dist/import-maps.d.ts +50 -0
- package/dist/import-maps.d.ts.map +1 -0
- package/dist/import-maps.js +246 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/route-maps.d.ts +46 -0
- package/dist/route-maps.d.ts.map +1 -0
- package/dist/route-maps.js +252 -0
- package/package.json +7 -4
- package/src/dynamic-loading.test.ts +426 -0
- package/src/dynamic-loading.ts +4 -4
- package/src/import-maps.test.ts +363 -0
- package/src/import-maps.ts +274 -0
- package/src/index.ts +2 -0
- package/src/route-maps.test.ts +481 -0
- package/src/route-maps.ts +285 -0
- package/vitest.config.ts +8 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-loading.d.ts","sourceRoot":"","sources":["../src/dynamic-loading.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-loading.d.ts","sourceRoot":"","sources":["../src/dynamic-loading.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAI1E;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,UAEnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CAAC,CAAC,GAAG,GAAG,EACzC,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAkB,EACzB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACA,OAAO,CAAC,CAAC,CAAC,CAyCZ;AAuBD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,SAAS,iBAoD3E;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,uBAExC"}
|
package/dist/dynamic-loading.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @module @category Dynamic Loading */ 'use strict';
|
|
2
|
-
// hack to make the types defined in esm-globals available here
|
|
3
2
|
import { dispatchToastShown } from "@openmrs/esm-globals";
|
|
4
3
|
import { getCoreTranslation } from "@openmrs/esm-translations";
|
|
4
|
+
import { getCurrentPageMap, getImportMapOverrideMap, resetImportMapOverrides } from "./import-maps.js";
|
|
5
5
|
/**
|
|
6
6
|
* @internal
|
|
7
7
|
*
|
|
@@ -108,7 +108,7 @@ import { getCoreTranslation } from "@openmrs/esm-translations";
|
|
|
108
108
|
if (url.startsWith('./')) {
|
|
109
109
|
url = window.spaBase + url.substring(1);
|
|
110
110
|
}
|
|
111
|
-
const isOverridden =
|
|
111
|
+
const isOverridden = jsPackage in getImportMapOverrideMap().imports;
|
|
112
112
|
try {
|
|
113
113
|
return await new Promise((resolve, reject)=>{
|
|
114
114
|
loadScript(url, resolve, reject);
|
|
@@ -123,7 +123,7 @@ import { getCoreTranslation } from "@openmrs/esm-translations";
|
|
|
123
123
|
}),
|
|
124
124
|
actionButtonLabel: getCoreTranslation('resetOverrides', 'Reset overrides'),
|
|
125
125
|
onActionButtonClick () {
|
|
126
|
-
|
|
126
|
+
resetImportMapOverrides();
|
|
127
127
|
window.location.reload();
|
|
128
128
|
}
|
|
129
129
|
});
|
|
@@ -140,7 +140,7 @@ import { getCoreTranslation } from "@openmrs/esm-translations";
|
|
|
140
140
|
*
|
|
141
141
|
* @returns The current import map.
|
|
142
142
|
*/ export async function getCurrentImportMap() {
|
|
143
|
-
return
|
|
143
|
+
return getCurrentPageMap();
|
|
144
144
|
}
|
|
145
145
|
function isFederatedModule(a) {
|
|
146
146
|
return typeof a === 'object' && a !== null && 'init' in a && typeof a['init'] === 'function' && 'get' in a && typeof a['get'] === 'function';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @module @category Import Map */
|
|
2
|
+
import type { ImportMap } from '@openmrs/esm-globals';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the import map for the current page. In dev mode, this merges
|
|
5
|
+
* the base map with the override snapshot captured at page load.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getCurrentPageMap(): Promise<ImportMap>;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the base import map from the DOM without any overrides applied.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getImportMapDefaultMap(): Promise<ImportMap>;
|
|
12
|
+
/**
|
|
13
|
+
* Returns what the import map will look like on the next page load, including
|
|
14
|
+
* any overrides that have been added/removed since the page loaded.
|
|
15
|
+
* In production, this is the same as the base map.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getImportMapNextPageMap(): Promise<ImportMap>;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the current override map. In production, returns empty imports.
|
|
20
|
+
* @param includeDisabled If true, includes disabled overrides.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getImportMapOverrideMap(includeDisabled?: boolean): ImportMap;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the list of module names whose overrides are disabled.
|
|
25
|
+
* In production, returns an empty array.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getImportMapDisabledOverrides(): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Returns whether the given module's override is disabled.
|
|
30
|
+
* In production, returns false.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isImportMapOverrideDisabled(moduleName: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Adds an import map override. In production, this is a no-op.
|
|
35
|
+
*/
|
|
36
|
+
export declare function addImportMapOverride(name: string, url: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Removes an import map override. In production, this is a no-op.
|
|
39
|
+
*/
|
|
40
|
+
export declare function removeImportMapOverride(name: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Clears all import map overrides. In production, this is a no-op.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resetImportMapOverrides(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Re-enables a previously disabled import map override. In production, this is a no-op.
|
|
47
|
+
*/
|
|
48
|
+
export declare function enableImportMapOverride(name: string): void;
|
|
49
|
+
export declare function setupImportMapOverrides(): void;
|
|
50
|
+
//# sourceMappingURL=import-maps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-maps.d.ts","sourceRoot":"","sources":["../src/import-maps.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AA2EtD;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAO5D;AAED;;GAEG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,SAAS,CAAC,CAEjE;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,SAAS,CAAC,CAMlE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAmB5E;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,EAAE,CAiBxD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAKvE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAWpE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiB1D;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAmB9C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgB1D;AAaD,wBAAgB,uBAAuB,SAWtC"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/** @module @category Import Map */ const OVERRIDE_PREFIX = 'import-map-override:';
|
|
2
|
+
const DISABLED_KEY = 'import-map-overrides-disabled';
|
|
3
|
+
const CHANGE_EVENT = 'import-map-overrides:change';
|
|
4
|
+
// Set by setupImportMapOverrides(); controls whether override functionality is active.
|
|
5
|
+
let devMode = false;
|
|
6
|
+
// Snapshot of overrides at setup time (matches import-map-overrides library behavior:
|
|
7
|
+
// getCurrentPageMap returns the overrides as they were when the page loaded).
|
|
8
|
+
let initialOverrideSnapshot = null;
|
|
9
|
+
/**
|
|
10
|
+
* Reads all `<script type="systemjs-importmap">` tags from the DOM and merges
|
|
11
|
+
* them into a single {@link ImportMap}. Tags with a `src` attribute are fetched;
|
|
12
|
+
* inline tags have their `textContent` parsed as JSON.
|
|
13
|
+
*/ async function readBaseMap() {
|
|
14
|
+
const scripts = document.querySelectorAll('script[type="systemjs-importmap"]');
|
|
15
|
+
const maps = [];
|
|
16
|
+
for(let i = 0; i < scripts.length; i++){
|
|
17
|
+
const script = scripts[i];
|
|
18
|
+
try {
|
|
19
|
+
if (script.src) {
|
|
20
|
+
const response = await fetch(script.src);
|
|
21
|
+
maps.push(await response.json());
|
|
22
|
+
} else if (script.textContent) {
|
|
23
|
+
maps.push(JSON.parse(script.textContent));
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.warn(`[import-maps] Failed to parse import map from script tag at index ${i}`, e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return mergeMaps(maps);
|
|
30
|
+
}
|
|
31
|
+
function mergeMaps(maps) {
|
|
32
|
+
const merged = {
|
|
33
|
+
imports: {}
|
|
34
|
+
};
|
|
35
|
+
for (const map of maps){
|
|
36
|
+
if (map?.imports) {
|
|
37
|
+
Object.assign(merged.imports, map.imports);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return merged;
|
|
41
|
+
}
|
|
42
|
+
/** Returns all `import-map-override:*` entries from localStorage as an {@link ImportMap}. */ function readOverrideMap() {
|
|
43
|
+
const imports = {};
|
|
44
|
+
try {
|
|
45
|
+
const disabled = getImportMapDisabledOverrides();
|
|
46
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
47
|
+
const key = localStorage.key(i);
|
|
48
|
+
if (key?.startsWith(OVERRIDE_PREFIX)) {
|
|
49
|
+
const moduleName = key.slice(OVERRIDE_PREFIX.length);
|
|
50
|
+
if (!disabled.includes(moduleName)) {
|
|
51
|
+
const url = localStorage.getItem(key);
|
|
52
|
+
if (url) {
|
|
53
|
+
imports[moduleName] = url;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn('[import-maps] Failed to read import-map overrides from localStorage', e);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
imports
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns the import map for the current page. In dev mode, this merges
|
|
67
|
+
* the base map with the override snapshot captured at page load.
|
|
68
|
+
*/ export async function getCurrentPageMap() {
|
|
69
|
+
const base = await readBaseMap();
|
|
70
|
+
if (!devMode) {
|
|
71
|
+
return base;
|
|
72
|
+
}
|
|
73
|
+
const overrides = initialOverrideSnapshot ?? readOverrideMap();
|
|
74
|
+
return mergeMaps([
|
|
75
|
+
base,
|
|
76
|
+
overrides
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns the base import map from the DOM without any overrides applied.
|
|
81
|
+
*/ export async function getImportMapDefaultMap() {
|
|
82
|
+
return readBaseMap();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns what the import map will look like on the next page load, including
|
|
86
|
+
* any overrides that have been added/removed since the page loaded.
|
|
87
|
+
* In production, this is the same as the base map.
|
|
88
|
+
*/ export async function getImportMapNextPageMap() {
|
|
89
|
+
if (!devMode) {
|
|
90
|
+
return readBaseMap();
|
|
91
|
+
}
|
|
92
|
+
const base = await readBaseMap();
|
|
93
|
+
return mergeMaps([
|
|
94
|
+
base,
|
|
95
|
+
readOverrideMap()
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns the current override map. In production, returns empty imports.
|
|
100
|
+
* @param includeDisabled If true, includes disabled overrides.
|
|
101
|
+
*/ export function getImportMapOverrideMap(includeDisabled) {
|
|
102
|
+
if (!devMode) {
|
|
103
|
+
return {
|
|
104
|
+
imports: {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (includeDisabled) {
|
|
108
|
+
const imports = {};
|
|
109
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
110
|
+
const key = localStorage.key(i);
|
|
111
|
+
if (key?.startsWith(OVERRIDE_PREFIX)) {
|
|
112
|
+
const url = localStorage.getItem(key);
|
|
113
|
+
if (url) {
|
|
114
|
+
imports[key.slice(OVERRIDE_PREFIX.length)] = url;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
imports
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return readOverrideMap();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Returns the list of module names whose overrides are disabled.
|
|
126
|
+
* In production, returns an empty array.
|
|
127
|
+
*/ export function getImportMapDisabledOverrides() {
|
|
128
|
+
if (!devMode) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const raw = localStorage.getItem(DISABLED_KEY);
|
|
133
|
+
if (raw) {
|
|
134
|
+
const parsed = JSON.parse(raw);
|
|
135
|
+
if (Array.isArray(parsed)) {
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// ignore malformed data
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Returns whether the given module's override is disabled.
|
|
146
|
+
* In production, returns false.
|
|
147
|
+
*/ export function isImportMapOverrideDisabled(moduleName) {
|
|
148
|
+
if (!devMode) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return getImportMapDisabledOverrides().includes(moduleName);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Adds an import map override. In production, this is a no-op.
|
|
155
|
+
*/ export function addImportMapOverride(name, url) {
|
|
156
|
+
if (!devMode) {
|
|
157
|
+
console.warn('[Security] Import map overrides are disabled in production mode.');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
localStorage.setItem(OVERRIDE_PREFIX + name, url);
|
|
162
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.warn('[import-maps] Failed to write import-map override to localStorage', e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Removes an import map override. In production, this is a no-op.
|
|
169
|
+
*/ export function removeImportMapOverride(name) {
|
|
170
|
+
if (!devMode) {
|
|
171
|
+
console.warn('[Security] Import map overrides are disabled in production mode.');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
localStorage.removeItem(OVERRIDE_PREFIX + name);
|
|
176
|
+
const disabled = getImportMapDisabledOverrides().filter((n)=>n !== name);
|
|
177
|
+
if (disabled.length > 0) {
|
|
178
|
+
localStorage.setItem(DISABLED_KEY, JSON.stringify(disabled));
|
|
179
|
+
} else {
|
|
180
|
+
localStorage.removeItem(DISABLED_KEY);
|
|
181
|
+
}
|
|
182
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.warn('[import-maps] Failed to update import-map overrides in localStorage', e);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Clears all import map overrides. In production, this is a no-op.
|
|
189
|
+
*/ export function resetImportMapOverrides() {
|
|
190
|
+
if (!devMode) {
|
|
191
|
+
console.warn('[Security] Import map overrides are disabled in production mode.');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const keysToRemove = [];
|
|
196
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
197
|
+
const key = localStorage.key(i);
|
|
198
|
+
if (key?.startsWith(OVERRIDE_PREFIX)) {
|
|
199
|
+
keysToRemove.push(key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
keysToRemove.forEach((key)=>localStorage.removeItem(key));
|
|
203
|
+
localStorage.removeItem(DISABLED_KEY);
|
|
204
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.warn('[import-maps] Failed to clear import-map overrides from localStorage', e);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Re-enables a previously disabled import map override. In production, this is a no-op.
|
|
211
|
+
*/ export function enableImportMapOverride(name) {
|
|
212
|
+
if (!devMode) {
|
|
213
|
+
console.warn('[Security] Import map overrides are disabled in production mode.');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const disabled = getImportMapDisabledOverrides().filter((n)=>n !== name);
|
|
218
|
+
if (disabled.length > 0) {
|
|
219
|
+
localStorage.setItem(DISABLED_KEY, JSON.stringify(disabled));
|
|
220
|
+
} else {
|
|
221
|
+
localStorage.removeItem(DISABLED_KEY);
|
|
222
|
+
}
|
|
223
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.warn('[import-maps] Failed to update import-map overrides in localStorage', e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Initializes the import map override system with mode-aware behavior.
|
|
230
|
+
*
|
|
231
|
+
* In production (`window.spaEnv !== 'development'`), mutation functions are
|
|
232
|
+
* no-ops and localStorage overrides are never consulted. In development, the
|
|
233
|
+
* full override workflow is available.
|
|
234
|
+
*
|
|
235
|
+
* Must be called before any code that depends on the import map functions.
|
|
236
|
+
*/ let initialized = false;
|
|
237
|
+
export function setupImportMapOverrides() {
|
|
238
|
+
if (initialized) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
initialized = true;
|
|
242
|
+
devMode = window.spaEnv === 'development';
|
|
243
|
+
if (devMode) {
|
|
244
|
+
initialOverrideSnapshot = readOverrideMap();
|
|
245
|
+
}
|
|
246
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** @module @category Route Map */
|
|
2
|
+
import { type OpenmrsAppRoutes, type OpenmrsRoutes } from '@openmrs/esm-globals';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the route map for the current page. In dev mode, this merges
|
|
5
|
+
* the base map with the override snapshot captured at page load.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getCurrentRouteMap(): Promise<OpenmrsRoutes>;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the base route map from the DOM without any overrides applied.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRouteMapDefaultMap(): Promise<OpenmrsRoutes>;
|
|
12
|
+
/**
|
|
13
|
+
* Returns what the route map will look like on the next page load, including
|
|
14
|
+
* any overrides that have been added/removed since the page loaded.
|
|
15
|
+
* In production, this is the same as the base map.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRouteMapNextPageMap(): Promise<OpenmrsRoutes>;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the current raw override entries from localStorage.
|
|
20
|
+
* In production, returns an empty object.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getRouteMapOverrideMap(): Record<string, string>;
|
|
23
|
+
/**
|
|
24
|
+
* Adds a route override. In production, this is a no-op.
|
|
25
|
+
* The app must be reloaded for overrides to take effect.
|
|
26
|
+
*
|
|
27
|
+
* @param moduleName The name of the module the routes are for
|
|
28
|
+
* @param routes Either an {@link OpenmrsAppRoutes} object, a string that represents a JSON
|
|
29
|
+
* version of an {@link OpenmrsAppRoutes} object, or a string or URL that resolves to a
|
|
30
|
+
* JSON document that represents an {@link OpenmrsAppRoutes} object
|
|
31
|
+
*/
|
|
32
|
+
export declare function addRouteMapOverride(moduleName: string, routes: OpenmrsAppRoutes | string | URL): void;
|
|
33
|
+
/**
|
|
34
|
+
* Removes a route override. In production, this is a no-op.
|
|
35
|
+
* The app must be reloaded for the removal to take effect.
|
|
36
|
+
*
|
|
37
|
+
* @param moduleName The module to remove the overrides for
|
|
38
|
+
*/
|
|
39
|
+
export declare function removeRouteMapOverride(moduleName: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Clears all route overrides. In production, this is a no-op.
|
|
42
|
+
* The app must be reloaded for the removal to take effect.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resetRouteMapOverrides(): void;
|
|
45
|
+
export declare function setupRouteMapOverrides(): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=route-maps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-maps.d.ts","sourceRoot":"","sources":["../src/route-maps.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,OAAO,EAAuC,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA2GtH;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,CAAC,CAOjE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,aAAa,CAAC,CAEpE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,aAAa,CAAC,CAMrE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoB/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,GAAG,QAkC9F;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,QAYxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,SAmBrC;AAaD,wBAAgB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBtD"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/** @module @category Route Map */ import { isOpenmrsAppRoutes, isOpenmrsRoutes } from "@openmrs/esm-globals";
|
|
2
|
+
const OVERRIDE_PREFIX = 'openmrs-routes:';
|
|
3
|
+
const CHANGE_EVENT = 'openmrs-routes:change';
|
|
4
|
+
// Set by setupRouteMapOverrides(); controls whether override functionality is active.
|
|
5
|
+
let devMode = false;
|
|
6
|
+
// Snapshot of overrides at setup time (mirrors the import-map-overrides pattern:
|
|
7
|
+
// getCurrentRouteMap returns the overrides as they were when the page loaded).
|
|
8
|
+
let initialOverrideSnapshot = null;
|
|
9
|
+
/**
|
|
10
|
+
* Reads all `<script type="openmrs-routes">` tags from the DOM and merges
|
|
11
|
+
* them into a single {@link OpenmrsRoutes} object. Tags with a `src` attribute
|
|
12
|
+
* are fetched; inline tags have their `textContent` parsed as JSON.
|
|
13
|
+
*/ async function readBaseMap() {
|
|
14
|
+
const scripts = document.querySelectorAll("script[type='openmrs-routes']");
|
|
15
|
+
const maps = [];
|
|
16
|
+
for(let i = 0; i < scripts.length; i++){
|
|
17
|
+
const script = scripts[i];
|
|
18
|
+
try {
|
|
19
|
+
let parsed;
|
|
20
|
+
if (script.src) {
|
|
21
|
+
const response = await fetch(script.src);
|
|
22
|
+
parsed = await response.json();
|
|
23
|
+
} else if (script.textContent) {
|
|
24
|
+
parsed = JSON.parse(script.textContent);
|
|
25
|
+
}
|
|
26
|
+
if (parsed && isOpenmrsRoutes(parsed)) {
|
|
27
|
+
maps.push(parsed);
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.warn(`[route-maps] Failed to parse routes from script tag at index ${i}`, e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return mergeRouteMaps(maps);
|
|
34
|
+
}
|
|
35
|
+
function mergeRouteMaps(maps) {
|
|
36
|
+
const merged = {};
|
|
37
|
+
for (const map of maps){
|
|
38
|
+
if (map && typeof map === 'object') {
|
|
39
|
+
Object.assign(merged, map);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return merged;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reads all `openmrs-routes:*` entries from localStorage as an {@link OpenmrsRoutes}.
|
|
46
|
+
* This is async because URL-valued overrides need to be fetched.
|
|
47
|
+
*/ async function readOverrideMap() {
|
|
48
|
+
const result = {};
|
|
49
|
+
try {
|
|
50
|
+
const entries = [];
|
|
51
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
52
|
+
const key = localStorage.key(i);
|
|
53
|
+
if (key?.startsWith(OVERRIDE_PREFIX)) {
|
|
54
|
+
const raw = localStorage.getItem(key);
|
|
55
|
+
if (raw) {
|
|
56
|
+
entries.push({
|
|
57
|
+
moduleName: key.slice(OVERRIDE_PREFIX.length),
|
|
58
|
+
raw
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const settled = await Promise.allSettled(entries.map(async ({ moduleName, raw })=>{
|
|
64
|
+
const parsed = JSON.parse(raw);
|
|
65
|
+
if (isOpenmrsAppRoutes(parsed)) {
|
|
66
|
+
return {
|
|
67
|
+
moduleName,
|
|
68
|
+
routes: parsed
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (typeof parsed === 'string' && parsed.startsWith('http')) {
|
|
72
|
+
const response = await fetch(parsed);
|
|
73
|
+
const fetched = await response.json();
|
|
74
|
+
if (isOpenmrsAppRoutes(fetched)) {
|
|
75
|
+
return {
|
|
76
|
+
moduleName,
|
|
77
|
+
routes: fetched
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`${parsed} did not resolve to a valid OpenmrsAppRoutes object`);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Override for ${moduleName} is neither a valid routes object nor a URL`);
|
|
83
|
+
}));
|
|
84
|
+
for (const entry of settled){
|
|
85
|
+
if (entry.status === 'fulfilled') {
|
|
86
|
+
result[entry.value.moduleName] = entry.value.routes;
|
|
87
|
+
} else {
|
|
88
|
+
console.warn('[route-maps] Failed to load route override', entry.reason);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.warn('[route-maps] Failed to read route overrides from localStorage', e);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Returns the route map for the current page. In dev mode, this merges
|
|
98
|
+
* the base map with the override snapshot captured at page load.
|
|
99
|
+
*/ export async function getCurrentRouteMap() {
|
|
100
|
+
const base = await readBaseMap();
|
|
101
|
+
if (!devMode) {
|
|
102
|
+
return base;
|
|
103
|
+
}
|
|
104
|
+
const overrides = initialOverrideSnapshot ?? await readOverrideMap();
|
|
105
|
+
return mergeRouteMaps([
|
|
106
|
+
base,
|
|
107
|
+
overrides
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns the base route map from the DOM without any overrides applied.
|
|
112
|
+
*/ export async function getRouteMapDefaultMap() {
|
|
113
|
+
return readBaseMap();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Returns what the route map will look like on the next page load, including
|
|
117
|
+
* any overrides that have been added/removed since the page loaded.
|
|
118
|
+
* In production, this is the same as the base map.
|
|
119
|
+
*/ export async function getRouteMapNextPageMap() {
|
|
120
|
+
if (!devMode) {
|
|
121
|
+
return readBaseMap();
|
|
122
|
+
}
|
|
123
|
+
const base = await readBaseMap();
|
|
124
|
+
return mergeRouteMaps([
|
|
125
|
+
base,
|
|
126
|
+
await readOverrideMap()
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Returns the current raw override entries from localStorage.
|
|
131
|
+
* In production, returns an empty object.
|
|
132
|
+
*/ export function getRouteMapOverrideMap() {
|
|
133
|
+
if (!devMode) {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
const result = {};
|
|
137
|
+
try {
|
|
138
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
139
|
+
const key = localStorage.key(i);
|
|
140
|
+
if (key?.startsWith(OVERRIDE_PREFIX)) {
|
|
141
|
+
const value = localStorage.getItem(key);
|
|
142
|
+
if (value) {
|
|
143
|
+
result[key.slice(OVERRIDE_PREFIX.length)] = value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.warn('[route-maps] Failed to read route overrides from localStorage', e);
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Adds a route override. In production, this is a no-op.
|
|
154
|
+
* The app must be reloaded for overrides to take effect.
|
|
155
|
+
*
|
|
156
|
+
* @param moduleName The name of the module the routes are for
|
|
157
|
+
* @param routes Either an {@link OpenmrsAppRoutes} object, a string that represents a JSON
|
|
158
|
+
* version of an {@link OpenmrsAppRoutes} object, or a string or URL that resolves to a
|
|
159
|
+
* JSON document that represents an {@link OpenmrsAppRoutes} object
|
|
160
|
+
*/ export function addRouteMapOverride(moduleName, routes) {
|
|
161
|
+
if (!devMode) {
|
|
162
|
+
console.warn('[Security] Route overrides are disabled outside development mode.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
if (typeof routes === 'string') {
|
|
167
|
+
if (routes.startsWith('http')) {
|
|
168
|
+
localStorage.setItem(OVERRIDE_PREFIX + moduleName, JSON.stringify(routes));
|
|
169
|
+
} else {
|
|
170
|
+
const maybeRoutes = JSON.parse(routes);
|
|
171
|
+
if (isOpenmrsAppRoutes(maybeRoutes)) {
|
|
172
|
+
localStorage.setItem(OVERRIDE_PREFIX + moduleName, JSON.stringify(maybeRoutes));
|
|
173
|
+
} else {
|
|
174
|
+
console.error(`The supplied routes for ${moduleName} is not a valid OpenmrsAppRoutes object`, routes);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (routes instanceof URL) {
|
|
179
|
+
localStorage.setItem(OVERRIDE_PREFIX + moduleName, JSON.stringify(routes.toString()));
|
|
180
|
+
} else if (isOpenmrsAppRoutes(routes)) {
|
|
181
|
+
localStorage.setItem(OVERRIDE_PREFIX + moduleName, JSON.stringify(routes));
|
|
182
|
+
} else {
|
|
183
|
+
console.error(`Override for ${moduleName} is not in a valid format. Expected either a Javascript Object, a JSON string of a Javascript object, or a URL`, routes);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.warn('[route-maps] Failed to write route override to localStorage', e);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Removes a route override. In production, this is a no-op.
|
|
193
|
+
* The app must be reloaded for the removal to take effect.
|
|
194
|
+
*
|
|
195
|
+
* @param moduleName The module to remove the overrides for
|
|
196
|
+
*/ export function removeRouteMapOverride(moduleName) {
|
|
197
|
+
if (!devMode) {
|
|
198
|
+
console.warn('[Security] Route overrides are disabled outside development mode.');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
localStorage.removeItem(OVERRIDE_PREFIX + moduleName);
|
|
203
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.warn('[route-maps] Failed to remove route override from localStorage', e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Clears all route overrides. In production, this is a no-op.
|
|
210
|
+
* The app must be reloaded for the removal to take effect.
|
|
211
|
+
*/ export function resetRouteMapOverrides() {
|
|
212
|
+
if (!devMode) {
|
|
213
|
+
console.warn('[Security] Route overrides are disabled outside development mode.');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const keysToRemove = [];
|
|
218
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
219
|
+
const key = localStorage.key(i);
|
|
220
|
+
if (key?.startsWith(OVERRIDE_PREFIX)) {
|
|
221
|
+
keysToRemove.push(key);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
keysToRemove.forEach((key)=>localStorage.removeItem(key));
|
|
225
|
+
window.dispatchEvent(new CustomEvent(CHANGE_EVENT));
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.warn('[route-maps] Failed to clear route overrides from localStorage', e);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Initializes the route map override system with mode-aware behavior.
|
|
232
|
+
*
|
|
233
|
+
* In production (`window.spaEnv !== 'development'`), mutation functions are
|
|
234
|
+
* no-ops and localStorage overrides are never consulted. In development, the
|
|
235
|
+
* full override workflow is available.
|
|
236
|
+
*
|
|
237
|
+
* Must be called before any code that depends on the route map functions.
|
|
238
|
+
*/ let setupPromise = null;
|
|
239
|
+
export function setupRouteMapOverrides() {
|
|
240
|
+
if (setupPromise) {
|
|
241
|
+
return setupPromise;
|
|
242
|
+
}
|
|
243
|
+
devMode = window.spaEnv === 'development';
|
|
244
|
+
if (devMode) {
|
|
245
|
+
setupPromise = readOverrideMap().then((snapshot)=>{
|
|
246
|
+
initialOverrideSnapshot = snapshot;
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
setupPromise = Promise.resolve();
|
|
250
|
+
}
|
|
251
|
+
return setupPromise;
|
|
252
|
+
}
|