@mu-cabin/opms-permission 0.1.0

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/index.mjs ADDED
@@ -0,0 +1,490 @@
1
+ // src/utils/storage.ts
2
+ var Storage = class {
3
+ setSystemId(systemId) {
4
+ this.systemId = systemId;
5
+ }
6
+ prefixKey(key) {
7
+ return this.systemId !== void 0 ? `systemId-${this.systemId}-${key}` : key;
8
+ }
9
+ /**
10
+ * Set an item in localStorage, with optional expiration in minutes
11
+ */
12
+ setItem(key, value, expireMinutes) {
13
+ const data = {
14
+ value,
15
+ expire: expireMinutes ? Date.now() + expireMinutes * 60 * 1e3 : null
16
+ };
17
+ localStorage.setItem(this.prefixKey(key), JSON.stringify(data));
18
+ }
19
+ /**
20
+ * Get an item from localStorage, returns undefined if expired or not found
21
+ */
22
+ getItem(key) {
23
+ const raw = localStorage.getItem(this.prefixKey(key));
24
+ if (!raw) return void 0;
25
+ try {
26
+ const data = JSON.parse(raw);
27
+ if (data.expire && Date.now() > data.expire) {
28
+ localStorage.removeItem(this.prefixKey(key));
29
+ return void 0;
30
+ }
31
+ return data.value;
32
+ } catch {
33
+ return void 0;
34
+ }
35
+ }
36
+ /**
37
+ * Remove an item from localStorage
38
+ */
39
+ removeItem(key) {
40
+ localStorage.removeItem(this.prefixKey(key));
41
+ }
42
+ };
43
+ var storage = new Storage();
44
+
45
+ // src/utils/dataHandler.ts
46
+ function getCompletePath(item) {
47
+ const menuUrl = item.openIndicator === "INNER_COMPONENT" ? item.componentPath : item.defaultUrl;
48
+ return menuUrl ?? "";
49
+ }
50
+ function getMenuName(item) {
51
+ return item.resourceType !== "FOLDER" ? item.resourceName || "" : (item.showName ?? item.resourceName) || "";
52
+ }
53
+ function createResourceMap(items) {
54
+ const resourceMap = {};
55
+ const widgetMap = {};
56
+ function addToMap(items2) {
57
+ items2.forEach((item) => {
58
+ const newItem = { ...item };
59
+ if (newItem.defaultResourceType === "WIDGET") {
60
+ if (newItem.componentPath) widgetMap[newItem.componentPath] = newItem;
61
+ } else {
62
+ const path = getCompletePath(newItem);
63
+ if (path) {
64
+ resourceMap[path] = newItem;
65
+ }
66
+ }
67
+ if (newItem.children) {
68
+ addToMap(newItem.children);
69
+ }
70
+ });
71
+ }
72
+ addToMap(items);
73
+ return { resourceMap, widgetMap };
74
+ }
75
+ function isMenuItem(item) {
76
+ const resourceType = item.resourceType ?? item.defaultResourceType;
77
+ return resourceType !== "WIDGET";
78
+ }
79
+ function createMenuList(items) {
80
+ const menus = [];
81
+ const menuMap = {};
82
+ const filterMenus = (items2, container) => {
83
+ items2.forEach((item) => {
84
+ const icon = item.icon || item.defaultIcon;
85
+ const path = getCompletePath(item);
86
+ const name = getMenuName(item);
87
+ const newItem = {
88
+ icon,
89
+ path,
90
+ name,
91
+ children: [],
92
+ openIndicator: item.openIndicator,
93
+ resourceId: item.resourceId
94
+ };
95
+ menuMap[newItem.resourceId] = newItem;
96
+ if (isMenuItem(item)) {
97
+ container.push(newItem);
98
+ }
99
+ if (item.children) {
100
+ filterMenus(item.children, newItem.children);
101
+ }
102
+ });
103
+ };
104
+ filterMenus(items, menus);
105
+ return { menuList: menus, menuMap };
106
+ }
107
+ function iterateNestedArray(items, callback) {
108
+ function trevel(items2, parent) {
109
+ return items2.map((item) => {
110
+ const handledItem = callback(item, parent);
111
+ if (handledItem && handledItem.children) {
112
+ handledItem.children = trevel(handledItem.children, handledItem);
113
+ }
114
+ return handledItem;
115
+ }).filter((v) => v);
116
+ }
117
+ return trevel(items);
118
+ }
119
+ function findFirstEnableCode(data) {
120
+ if (!Array.isArray(data) || data.length === 0) {
121
+ return null;
122
+ }
123
+ let queue = [...data];
124
+ while (queue.length > 0) {
125
+ const node = queue.shift();
126
+ if (!node?.disabled) {
127
+ return node?.value || null;
128
+ }
129
+ if (Array.isArray(node.children) && node.children.length > 0) {
130
+ queue = queue.concat(node.children);
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ function findAllEnableTopLevelCodes(nodes) {
136
+ const codes = [];
137
+ function findEnabledNodes(nodes2) {
138
+ for (const node of nodes2) {
139
+ if (!node.disabled) {
140
+ codes.push(node.value);
141
+ } else if (node.children) {
142
+ findEnabledNodes(node.children);
143
+ }
144
+ }
145
+ }
146
+ findEnabledNodes(nodes);
147
+ return codes;
148
+ }
149
+
150
+ // src/api.ts
151
+ import axios from "axios";
152
+
153
+ // src/config.ts
154
+ var USER_INFO_KEY = "opms_user_info";
155
+ var RESOURCE_KEY = "opms_resources";
156
+ var TOKEN_KEY = "omps_authorization";
157
+ var ORG_COMPANY_KEY = "opms_org_company";
158
+
159
+ // src/api.ts
160
+ var axiosClient = axios.create();
161
+ axiosClient.interceptors.request.use((config) => {
162
+ const token = storage.getItem(TOKEN_KEY);
163
+ if (token) {
164
+ config.headers.Authorization = token;
165
+ }
166
+ return config;
167
+ });
168
+ axiosClient.interceptors.response.use(
169
+ (response) => {
170
+ const res = response.data;
171
+ const { code, msg } = res;
172
+ if (code !== 200) {
173
+ throw new Error(msg);
174
+ }
175
+ return res;
176
+ },
177
+ (error) => {
178
+ return Promise.reject(error);
179
+ }
180
+ );
181
+ async function login(baseUrl, authorizationCode) {
182
+ return await axiosClient.get(
183
+ `${baseUrl}/opmsDefaultAuth/oauthLogin`,
184
+ {
185
+ params: { authorizationCode }
186
+ }
187
+ );
188
+ }
189
+ async function logout(baseUrl) {
190
+ return await axiosClient.post(
191
+ `${baseUrl}/opmsDefaultAuth/logout`
192
+ );
193
+ }
194
+ async function getUserInfo(baseUrl) {
195
+ return await axiosClient.post(
196
+ `${baseUrl}/opmsDefaultUser/userInfo`,
197
+ {}
198
+ );
199
+ }
200
+ async function queryResource(baseUrl, params) {
201
+ return await axiosClient.post(
202
+ `${baseUrl}/opmsDefaultUser/userResource`,
203
+ params
204
+ );
205
+ }
206
+ async function getUserOrgTree(baseUrl, params) {
207
+ return await axiosClient.post(
208
+ `${baseUrl}/opmsDefaultUser/orgTree`,
209
+ params
210
+ );
211
+ }
212
+ async function queryOrgCompanies(baseUrl, params) {
213
+ return await axiosClient.post(
214
+ `${baseUrl}/opmsDefaultUser/branches`,
215
+ params
216
+ );
217
+ }
218
+
219
+ // src/permission.ts
220
+ var Permission = class {
221
+ constructor(options) {
222
+ this._userInfo = null;
223
+ this._orgTree = null;
224
+ this._orgCompany = null;
225
+ this.resources = [];
226
+ this.resourceMap = {};
227
+ this.widgetMap = {};
228
+ this.menuList = [];
229
+ this.menuMap = {};
230
+ this.baseUrl = options.baseUrl;
231
+ this.systemId = options.systemId;
232
+ storage.setSystemId(this.systemId);
233
+ }
234
+ /**
235
+ * Login using code from URL, save userInfo
236
+ */
237
+ async login() {
238
+ const url = new URL(window.location.href);
239
+ const code = url.searchParams.get("code");
240
+ if (!code) throw new Error("No code found in URL");
241
+ const { obj, success, msg } = await login(this.baseUrl, code);
242
+ if (!success) {
243
+ throw new Error(msg);
244
+ }
245
+ const { token } = obj;
246
+ url.searchParams.delete("code");
247
+ storage.setItem(TOKEN_KEY, token);
248
+ return token;
249
+ }
250
+ /**
251
+ * Logout and clear userInfo
252
+ */
253
+ async logout() {
254
+ await logout(this.baseUrl);
255
+ this.clear();
256
+ }
257
+ clear() {
258
+ this._userInfo = null;
259
+ this._orgTree = null;
260
+ this._orgCompany = null;
261
+ this.resources = [];
262
+ this.resourceMap = {};
263
+ this.widgetMap = {};
264
+ this.menuList = [];
265
+ this.menuMap = {};
266
+ storage.removeItem(RESOURCE_KEY);
267
+ storage.removeItem(TOKEN_KEY);
268
+ storage.removeItem(USER_INFO_KEY);
269
+ storage.removeItem(ORG_COMPANY_KEY);
270
+ }
271
+ async getUserInfo() {
272
+ const data = await getUserInfo(this.baseUrl);
273
+ const { obj, success, msg } = data;
274
+ if (!success) {
275
+ return Promise.reject(new Error(msg));
276
+ }
277
+ this._userInfo = obj;
278
+ return obj;
279
+ }
280
+ /**
281
+ * Get resources and process to menuList, menuMap, widgetMap (matches app store logic)
282
+ */
283
+ async getResources() {
284
+ const resourcesObj = storage.getItem(RESOURCE_KEY) || null;
285
+ if (resourcesObj) {
286
+ const { menuList: menuList2, menuMap: menuMap2 } = createMenuList(resourcesObj.resources);
287
+ return {
288
+ resources: resourcesObj.resources,
289
+ resourceMap: resourcesObj.resourceMap,
290
+ widgetMap: resourcesObj.widgetMap,
291
+ menuList: menuList2,
292
+ menuMap: menuMap2
293
+ };
294
+ }
295
+ const { obj, success, msg } = await queryResource(this.baseUrl, {
296
+ systemId: this.systemId
297
+ });
298
+ if (!success) {
299
+ return Promise.reject(new Error(msg));
300
+ }
301
+ const resources = obj;
302
+ const { resourceMap, widgetMap } = createResourceMap(resources);
303
+ const { menuList, menuMap } = createMenuList(resources);
304
+ this.resources = resources;
305
+ this.resourceMap = resourceMap;
306
+ this.widgetMap = widgetMap;
307
+ this.menuList = menuList;
308
+ this.menuMap = menuMap;
309
+ storage.setItem(
310
+ RESOURCE_KEY,
311
+ {
312
+ resources,
313
+ resourceMap,
314
+ widgetMap
315
+ },
316
+ 60 * 24
317
+ // 24 hours
318
+ );
319
+ return {
320
+ resources,
321
+ resourceMap,
322
+ widgetMap,
323
+ menuList,
324
+ menuMap
325
+ };
326
+ }
327
+ /**
328
+ * Query and process organization tree
329
+ */
330
+ async queryOrgs() {
331
+ try {
332
+ const data = await getUserOrgTree(this.baseUrl, {
333
+ systemId: this.systemId,
334
+ name: "COS"
335
+ });
336
+ const { obj, success, msg } = data;
337
+ if (!success) {
338
+ return Promise.reject(new Error(msg));
339
+ }
340
+ const orgTree = obj;
341
+ const newTree = iterateNestedArray([orgTree], (item) => {
342
+ return {
343
+ label: item.orgShortName,
344
+ value: item.orgCode,
345
+ title: item.orgShortName,
346
+ key: item.orgCode,
347
+ nodeLevel: item.nodeLevel,
348
+ children: item.children,
349
+ disabled: !item.hasPermission,
350
+ orgType: item.orgType
351
+ };
352
+ })?.[0];
353
+ this._orgTree = newTree;
354
+ return newTree;
355
+ } catch (error) {
356
+ console.log(error);
357
+ return null;
358
+ }
359
+ }
360
+ async queryCompanies() {
361
+ try {
362
+ let orgCompanyList = storage.getItem(ORG_COMPANY_KEY);
363
+ if (!orgCompanyList) {
364
+ const { obj } = await queryOrgCompanies(this.baseUrl, { queryAllBranches: true });
365
+ orgCompanyList = obj;
366
+ storage.setItem(ORG_COMPANY_KEY, orgCompanyList);
367
+ }
368
+ this._orgCompany = orgCompanyList;
369
+ } catch (error) {
370
+ console.log(error);
371
+ }
372
+ }
373
+ isLogin() {
374
+ return !!storage.getItem(TOKEN_KEY);
375
+ }
376
+ getToken() {
377
+ return storage.getItem(TOKEN_KEY);
378
+ }
379
+ // --- Getters ---
380
+ get userInfo() {
381
+ if (!this._userInfo) {
382
+ this._userInfo = storage.getItem(USER_INFO_KEY);
383
+ }
384
+ if (!this._userInfo) return null;
385
+ const { account, name, ehrId, crewCode, crewId } = this._userInfo;
386
+ return { account, name, ehrId, crewCode, crewId };
387
+ }
388
+ get hasRootAuth() {
389
+ const orgs = this._userInfo?.organizations || [];
390
+ const first = orgs[0];
391
+ if (!first) return false;
392
+ return first.searchPath === "#1";
393
+ }
394
+ get userOrganizations() {
395
+ return this._userInfo?.userOrganizations || [];
396
+ }
397
+ get allCompanyOptions() {
398
+ const orgCompany = this._orgCompany;
399
+ if (!orgCompany) return [];
400
+ const list = orgCompany.map((v) => ({
401
+ label: v.orgName,
402
+ value: v.orgCode
403
+ }));
404
+ list.unshift({ label: "\u5168\u516C\u53F8", value: "EA" });
405
+ return list;
406
+ }
407
+ get companyOptions() {
408
+ const orgTree = this._orgTree;
409
+ if (!orgTree) return [];
410
+ const list = (orgTree.children || []).map(
411
+ ({ children, ...others }) => ({ ...others, disabled: false })
412
+ );
413
+ if (this.hasRootAuth) {
414
+ list.unshift({ ...orgTree, label: "\u5168\u516C\u53F8", children: [] });
415
+ }
416
+ return list;
417
+ }
418
+ get firstCompanyOrgCode() {
419
+ return this.companyOptions?.[0]?.value ?? "";
420
+ }
421
+ get unitOptions() {
422
+ const orgTree = this._orgTree;
423
+ if (!orgTree) return [];
424
+ return orgTree.disabled ? orgTree.children ?? [] : [orgTree];
425
+ }
426
+ get allUnitOptions() {
427
+ const orgTree = this._orgTree;
428
+ if (!orgTree) return [];
429
+ const newTree = iterateNestedArray(
430
+ [orgTree],
431
+ ({ disabled, ...other }) => ({ ...other })
432
+ )?.[0];
433
+ return newTree ? newTree.children ?? [] : [];
434
+ }
435
+ get firstUnitOrgCode() {
436
+ if (this.hasRootAuth) {
437
+ return this.companyOptions[1]?.value ?? "";
438
+ }
439
+ const orgTree = this._orgTree;
440
+ if (!orgTree) return "";
441
+ return findFirstEnableCode([orgTree]) ?? "";
442
+ }
443
+ get topLevelUnitOrgCodes() {
444
+ const orgTree = this._orgTree;
445
+ if (!orgTree) return [];
446
+ return findAllEnableTopLevelCodes(
447
+ Array.isArray(orgTree) ? orgTree : [orgTree]
448
+ );
449
+ }
450
+ };
451
+
452
+ // src/sso.ts
453
+ function jumpToSSOLogin({
454
+ baseUrl,
455
+ redirectUrl,
456
+ clientId = "testSA19"
457
+ }) {
458
+ const { location } = window;
459
+ const ssoUrl = new URL(`${baseUrl}/idp/oauth2/authorize`);
460
+ const defaultUrl = new URL(
461
+ location.origin + location.pathname + location.search
462
+ );
463
+ ssoUrl.searchParams.set("client_id", clientId);
464
+ ssoUrl.searchParams.set("response_type", "code");
465
+ ssoUrl.searchParams.set("redirect_uri", redirectUrl || defaultUrl.toString());
466
+ window.location.href = ssoUrl.toString();
467
+ }
468
+ function jumpToSSOLogout({
469
+ baseUrl,
470
+ redirectToUrl,
471
+ clientId = "testSA19"
472
+ }) {
473
+ if (!baseUrl) {
474
+ throw new Error("baseUrl is required");
475
+ }
476
+ const { location } = window;
477
+ const ssoLoginUrl = `${baseUrl}/idp/oauth2/authorize?client_id=${clientId}&response_type=code&redirect_uri=${location.origin}/`;
478
+ const redirectUrl = encodeURIComponent(redirectToUrl || ssoLoginUrl);
479
+ const logoutUrl = new URL(`${baseUrl}/idp/profile/OAUTH2/Redirect/GLO`);
480
+ logoutUrl.searchParams.set("response_type", "code");
481
+ logoutUrl.searchParams.set("entityId", clientId);
482
+ logoutUrl.searchParams.set("redirectToLogin", "true");
483
+ logoutUrl.searchParams.set("redirctToUrl", redirectUrl);
484
+ window.location.href = logoutUrl.toString();
485
+ }
486
+ export {
487
+ Permission as OpmsPermission,
488
+ jumpToSSOLogin,
489
+ jumpToSSOLogout
490
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@mu-cabin/opms-permission",
3
+ "version": "0.1.0",
4
+ "description": "Frontend SDK for OPMS permission and auth management.",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.mjs.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": ["dist"],
9
+ "scripts": {
10
+ "build": "tsup src/index.ts --dts --format cjs,esm",
11
+ "dev": "tsup src/index.ts --watch --dts --format cjs,esm",
12
+ "clean": "rimraf dist",
13
+ "prepublishOnly": "npm run build",
14
+ "publish:package": "npm run clean && npm run build && npm publish"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "keywords": ["opms", "permission", "auth", "sdk"],
20
+ "author": "",
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "axios": "^1.6.0",
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.0.0",
26
+ "rimraf": "^5.0.5"
27
+ },
28
+ "dependencies": {}
29
+ }