com-angel-authorization 1.0.2 → 1.0.3

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.
@@ -23,25 +23,25 @@ var react_exports = {};
23
23
  __export(react_exports, {
24
24
  Can: () => Can,
25
25
  PermissionProvider: () => PermissionProvider,
26
- PermissionStore: () => import_core2.PermissionStore,
27
- RESOURCE_PRIVATE_OPTIONS: () => import_resources2.RESOURCE_PRIVATE_OPTIONS,
28
- RESOURCE_STATUS_DISABLED: () => import_resources2.RESOURCE_STATUS_DISABLED,
29
- RESOURCE_STATUS_ENABLED: () => import_resources2.RESOURCE_STATUS_ENABLED,
30
- RESOURCE_STATUS_OPTIONS: () => import_resources2.RESOURCE_STATUS_OPTIONS,
31
- RESOURCE_TYPE_API: () => import_resources2.RESOURCE_TYPE_API,
26
+ PermissionStore: () => PermissionStore,
27
+ RESOURCE_PRIVATE_OPTIONS: () => RESOURCE_PRIVATE_OPTIONS,
28
+ RESOURCE_STATUS_DISABLED: () => RESOURCE_STATUS_DISABLED,
29
+ RESOURCE_STATUS_ENABLED: () => RESOURCE_STATUS_ENABLED,
30
+ RESOURCE_STATUS_OPTIONS: () => RESOURCE_STATUS_OPTIONS,
31
+ RESOURCE_TYPE_API: () => RESOURCE_TYPE_API,
32
32
  ResourceManager: () => ResourceManager,
33
- appendQueryParam: () => import_resources2.appendQueryParam,
34
- buildCreateBody: () => import_resources2.buildCreateBody,
35
- buildUpdateBody: () => import_resources2.buildUpdateBody,
36
- createAuthorizationResourceApi: () => import_resources2.createAuthorizationResourceApi,
37
- createDefaultResourceRequest: () => import_resources2.createDefaultResourceRequest,
38
- createPermissionStore: () => import_core2.createPermissionStore,
39
- extractPagination: () => import_resources2.extractPagination,
40
- extractRecords: () => import_resources2.extractRecords,
41
- getAppClientId: () => import_snowflake.getAppClientId,
42
- getDeviceWorkerId: () => import_snowflake.getDeviceWorkerId,
43
- mapAuthorizationResource: () => import_resources2.mapAuthorizationResource,
44
- snowyflake: () => import_snowflake.snowyflake,
33
+ appendQueryParam: () => appendQueryParam,
34
+ buildCreateBody: () => buildCreateBody,
35
+ buildUpdateBody: () => buildUpdateBody,
36
+ createAuthorizationResourceApi: () => createAuthorizationResourceApi,
37
+ createDefaultResourceRequest: () => createDefaultResourceRequest,
38
+ createPermissionStore: () => createPermissionStore,
39
+ extractPagination: () => extractPagination,
40
+ extractRecords: () => extractRecords,
41
+ getAppClientId: () => getAppClientId,
42
+ getDeviceWorkerId: () => getDeviceWorkerId,
43
+ mapAuthorizationResource: () => mapAuthorizationResource,
44
+ snowyflake: () => snowyflake,
45
45
  useHasPermission: () => useHasPermission,
46
46
  useHasRole: () => useHasRole,
47
47
  usePermission: () => usePermission
@@ -50,7 +50,112 @@ module.exports = __toCommonJS(react_exports);
50
50
 
51
51
  // src/react/context.tsx
52
52
  var import_react = require("react");
53
- var import_core = require("../core");
53
+
54
+ // src/core/store.ts
55
+ var DEFAULT_STATE = {
56
+ permissions: [],
57
+ roles: [],
58
+ isSuperAdmin: false
59
+ };
60
+ function toArray(value) {
61
+ return Array.isArray(value) ? value : [value];
62
+ }
63
+ function matchCodes(owned, required, mode, isSuperAdmin) {
64
+ if (isSuperAdmin) return true;
65
+ if (required.length === 0) return true;
66
+ if (mode === "all") {
67
+ return required.every((code) => owned.has(code));
68
+ }
69
+ return required.some((code) => owned.has(code));
70
+ }
71
+ var PermissionStore = class {
72
+ constructor(initial) {
73
+ this.listeners = /* @__PURE__ */ new Set();
74
+ this.state = {
75
+ ...DEFAULT_STATE,
76
+ ...initial,
77
+ permissions: initial?.permissions ?? [],
78
+ roles: initial?.roles ?? []
79
+ };
80
+ this.permissionSet = new Set(this.state.permissions);
81
+ this.roleSet = new Set(this.state.roles);
82
+ }
83
+ getState() {
84
+ return {
85
+ permissions: [...this.state.permissions],
86
+ roles: [...this.state.roles],
87
+ isSuperAdmin: this.state.isSuperAdmin
88
+ };
89
+ }
90
+ setState(next) {
91
+ this.state = {
92
+ permissions: next.permissions ?? this.state.permissions,
93
+ roles: next.roles ?? this.state.roles,
94
+ isSuperAdmin: next.isSuperAdmin ?? this.state.isSuperAdmin
95
+ };
96
+ this.permissionSet = new Set(this.state.permissions);
97
+ this.roleSet = new Set(this.state.roles);
98
+ this.emit();
99
+ }
100
+ setPermissions(permissions) {
101
+ this.setState({ permissions });
102
+ }
103
+ setRoles(roles) {
104
+ this.setState({ roles });
105
+ }
106
+ setSuperAdmin(isSuperAdmin) {
107
+ this.setState({ isSuperAdmin });
108
+ }
109
+ /** Replace permissions, roles and super-admin flag in one shot (typical after login). */
110
+ hydrate(payload) {
111
+ this.setState({
112
+ permissions: payload.permissions ?? [],
113
+ roles: payload.roles ?? [],
114
+ isSuperAdmin: payload.isSuperAdmin ?? false
115
+ });
116
+ }
117
+ /** Clear all authz data (typical after logout). */
118
+ clear() {
119
+ this.hydrate({});
120
+ }
121
+ hasPermission(codes, options = {}) {
122
+ const mode = options.mode ?? "any";
123
+ return matchCodes(
124
+ this.permissionSet,
125
+ toArray(codes).filter(Boolean),
126
+ mode,
127
+ Boolean(this.state.isSuperAdmin)
128
+ );
129
+ }
130
+ hasRole(codes, options = {}) {
131
+ const mode = options.mode ?? "any";
132
+ return matchCodes(
133
+ this.roleSet,
134
+ toArray(codes).filter(Boolean),
135
+ mode,
136
+ Boolean(this.state.isSuperAdmin)
137
+ );
138
+ }
139
+ /** Alias of `hasPermission` for template-friendly APIs. */
140
+ can(codes, options) {
141
+ return this.hasPermission(codes, options);
142
+ }
143
+ subscribe(listener) {
144
+ this.listeners.add(listener);
145
+ return () => {
146
+ this.listeners.delete(listener);
147
+ };
148
+ }
149
+ emit() {
150
+ const snapshot = this.getState();
151
+ this.listeners.forEach((listener) => listener(snapshot));
152
+ }
153
+ };
154
+ function createPermissionStore(initial) {
155
+ return new PermissionStore(initial);
156
+ }
157
+
158
+ // src/react/context.tsx
54
159
  var PermissionContext = (0, import_react.createContext)(null);
55
160
  function PermissionProvider({
56
161
  children,
@@ -58,7 +163,7 @@ function PermissionProvider({
58
163
  initialState
59
164
  }) {
60
165
  const store = (0, import_react.useMemo)(
61
- () => externalStore ?? (0, import_core.createPermissionStore)(initialState),
166
+ () => externalStore ?? createPermissionStore(initialState),
62
167
  // store identity is intentional; initialState only applies on first create
63
168
  // eslint-disable-next-line react-hooks/exhaustive-deps
64
169
  [externalStore]
@@ -160,13 +265,293 @@ function Can({
160
265
 
161
266
  // src/react/ResourceManager.tsx
162
267
  var import_react2 = require("react");
163
- var import_resources = require("../resources");
268
+
269
+ // src/resources/types.ts
270
+ var RESOURCE_TYPE_API = "api";
271
+ var RESOURCE_STATUS_ENABLED = 1;
272
+ var RESOURCE_STATUS_DISABLED = 0;
273
+ var RESOURCE_STATUS_OPTIONS = [
274
+ { label: "\u542F\u7528", value: RESOURCE_STATUS_ENABLED },
275
+ { label: "\u7981\u7528", value: RESOURCE_STATUS_DISABLED }
276
+ ];
277
+ var RESOURCE_PRIVATE_OPTIONS = [
278
+ { label: "\u662F", value: true },
279
+ { label: "\u5426", value: false }
280
+ ];
281
+
282
+ // src/utils/snowflake.ts
283
+ var DEVICE_WORKER_ID_KEY = "hgams_device_worker_id";
284
+ function generateDeviceWorkerId() {
285
+ const cached = localStorage.getItem(DEVICE_WORKER_ID_KEY);
286
+ if (cached !== null && cached !== "") {
287
+ return BigInt(cached);
288
+ }
289
+ const components = [
290
+ navigator.userAgent,
291
+ screen.width + "x" + screen.height,
292
+ screen.colorDepth,
293
+ navigator.language,
294
+ (/* @__PURE__ */ new Date()).getTimezoneOffset(),
295
+ navigator.hardwareConcurrency,
296
+ navigator.deviceMemory
297
+ ];
298
+ const fingerprint = components.join("|");
299
+ let hash = 0;
300
+ for (let i = 0; i < fingerprint.length; i++) {
301
+ hash = (hash << 5) - hash + fingerprint.charCodeAt(i);
302
+ hash |= 0;
303
+ }
304
+ const maxWorkerId = Math.pow(2, 14) - 1;
305
+ const workerId = Math.abs(hash % maxWorkerId);
306
+ const workerIdBigInt = BigInt(workerId);
307
+ localStorage.setItem(DEVICE_WORKER_ID_KEY, workerIdBigInt.toString());
308
+ return workerIdBigInt;
309
+ }
310
+ var ConfigurableSnowflake = class {
311
+ constructor({
312
+ epoch = 0n,
313
+ workerId = 0n,
314
+ processId = 0n,
315
+ workerIdBits = 5,
316
+ processIdBits = 5,
317
+ sequenceBits = 12
318
+ } = {}) {
319
+ this.sequence = 0n;
320
+ this.latestTimestamp = BigInt(Date.now());
321
+ this.epoch = epoch;
322
+ const workerIdBitsBig = BigInt(workerIdBits);
323
+ const processIdBitsBig = BigInt(processIdBits);
324
+ const sequenceBitsBig = BigInt(sequenceBits);
325
+ const workerIdMask = -1n ^ -1n << workerIdBitsBig;
326
+ const processIdMask = -1n ^ -1n << processIdBitsBig;
327
+ this.sequenceMask = -1n ^ -1n << sequenceBitsBig;
328
+ this.timestampLeftShift = sequenceBitsBig + workerIdBitsBig + processIdBitsBig;
329
+ this.workerIdShift = sequenceBitsBig + processIdBitsBig;
330
+ this.processIdShift = sequenceBitsBig;
331
+ this.workerId = workerId & workerIdMask;
332
+ this.processId = processId & processIdMask;
333
+ }
334
+ nextId() {
335
+ const timestamp = BigInt(Date.now());
336
+ if (this.latestTimestamp === timestamp) {
337
+ this.sequence = this.sequence + 1n & this.sequenceMask;
338
+ } else {
339
+ this.sequence = 0n;
340
+ this.latestTimestamp = timestamp;
341
+ }
342
+ return timestamp - this.epoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.processId << this.processIdShift | this.sequence;
343
+ }
344
+ };
345
+ var snowyflake = new ConfigurableSnowflake({
346
+ workerId: generateDeviceWorkerId(),
347
+ workerIdBits: 14,
348
+ processIdBits: 0,
349
+ sequenceBits: 8,
350
+ epoch: BigInt((/* @__PURE__ */ new Date("2026-06-08")).getTime())
351
+ });
352
+ function getAppClientId() {
353
+ return snowyflake.nextId().toString();
354
+ }
355
+ function getDeviceWorkerId() {
356
+ return snowyflake.workerId.toString();
357
+ }
358
+
359
+ // src/resources/pagination.ts
360
+ function appendQueryParam(parts, key, value) {
361
+ if (value !== void 0 && value !== null && String(value) !== "") {
362
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
363
+ }
364
+ }
365
+ function extractPagination(payload) {
366
+ if (!payload || typeof payload !== "object") {
367
+ return {
368
+ pageToken: "",
369
+ hasPreviousPage: false,
370
+ hasNextPage: false
371
+ };
372
+ }
373
+ const envelope = payload;
374
+ const pagination = envelope.pagination && typeof envelope.pagination === "object" ? envelope.pagination : envelope.page && typeof envelope.page === "object" ? envelope.page : null;
375
+ const rawPageToken = pagination?.["page-token"] ?? pagination?.pageToken ?? pagination?.nextPageToken ?? envelope["page-token"] ?? envelope.pageToken ?? "";
376
+ const pageToken = rawPageToken === void 0 || rawPageToken === null ? "" : String(rawPageToken);
377
+ const hasPreviousPage = Boolean(
378
+ pagination?.hasPreviousPage ?? pagination?.hasPrevious ?? pagination?.hasPre ?? pagination?.previous ?? envelope.hasPreviousPage ?? envelope.hasPrevious
379
+ );
380
+ const hasNextPage = Boolean(
381
+ pagination?.hasNextPage ?? pagination?.hasNext ?? pagination?.next ?? envelope.hasNextPage ?? envelope.hasNext
382
+ );
383
+ return {
384
+ pageToken,
385
+ hasPreviousPage,
386
+ hasNextPage
387
+ };
388
+ }
389
+ function extractRecords(payload) {
390
+ if (!payload) return [];
391
+ if (Array.isArray(payload)) return payload;
392
+ if (typeof payload === "object") {
393
+ const envelope = payload;
394
+ const data = envelope.data;
395
+ if (Array.isArray(data)) return data;
396
+ if (data && typeof data === "object") {
397
+ const nested = data;
398
+ if (Array.isArray(nested.records)) return nested.records;
399
+ if (Array.isArray(nested.items)) return nested.items;
400
+ if (Array.isArray(nested.list)) return nested.list;
401
+ if (Array.isArray(nested.content)) return nested.content;
402
+ }
403
+ if (Array.isArray(envelope.records)) return envelope.records;
404
+ if (Array.isArray(envelope.items)) return envelope.items;
405
+ if (Array.isArray(envelope.list)) return envelope.list;
406
+ if (Array.isArray(envelope.content)) return envelope.content;
407
+ }
408
+ return [];
409
+ }
410
+
411
+ // src/resources/api.ts
412
+ function toResourceStatus(value) {
413
+ const n = Number(value);
414
+ return n === RESOURCE_STATUS_ENABLED ? RESOURCE_STATUS_ENABLED : RESOURCE_STATUS_DISABLED;
415
+ }
416
+ function toBoolean(value) {
417
+ if (typeof value === "boolean") return value;
418
+ if (typeof value === "number") return value !== 0;
419
+ if (typeof value === "string") {
420
+ const v = value.trim().toLowerCase();
421
+ return v === "true" || v === "1" || v === "yes" || v === "\u662F";
422
+ }
423
+ return false;
424
+ }
425
+ function mapAuthorizationResource(item) {
426
+ if (!item || typeof item !== "object") return null;
427
+ const row = item;
428
+ const resourceId = row.resourceId ?? row.resource_id ?? row.id;
429
+ if (resourceId === void 0 || resourceId === null || resourceId === "") return null;
430
+ return {
431
+ resourceId: String(resourceId),
432
+ type: RESOURCE_TYPE_API,
433
+ name: String(row.name ?? ""),
434
+ identification: String(row.identification ?? row.identity ?? ""),
435
+ status: toResourceStatus(row.status),
436
+ private: toBoolean(row.private ?? row.isPrivate ?? row.is_private),
437
+ description: String(row.description ?? row.desc ?? "")
438
+ };
439
+ }
440
+ function buildListUrl(params) {
441
+ const parts = [];
442
+ appendQueryParam(parts, "type", RESOURCE_TYPE_API);
443
+ appendQueryParam(parts, "page-token", params?.pagination?.pageToken ?? "");
444
+ appendQueryParam(parts, "page-size", params?.pagination?.pageSize ?? "20");
445
+ appendQueryParam(parts, "page-direction", params?.pagination?.pageDirection ?? "current");
446
+ return `/authorization-resources?${parts.join("&")}`;
447
+ }
448
+ function buildCreateBody(values, resourceId = getAppClientId()) {
449
+ return {
450
+ resourceId,
451
+ type: RESOURCE_TYPE_API,
452
+ name: values.name.trim(),
453
+ identification: values.identification.trim(),
454
+ status: values.status,
455
+ private: values.private,
456
+ description: values.description.trim()
457
+ };
458
+ }
459
+ function buildUpdateBody(values) {
460
+ return {
461
+ type: RESOURCE_TYPE_API,
462
+ name: values.name.trim(),
463
+ identification: values.identification.trim(),
464
+ status: values.status,
465
+ private: values.private,
466
+ description: values.description.trim()
467
+ };
468
+ }
469
+ function createAuthorizationResourceApi(request) {
470
+ return {
471
+ async list(params, signal) {
472
+ const json = await request({
473
+ method: "GET",
474
+ url: buildListUrl(params),
475
+ signal
476
+ });
477
+ const records = extractRecords(json).map(mapAuthorizationResource).filter((item) => item !== null);
478
+ const pagination = extractPagination(json);
479
+ return {
480
+ records,
481
+ ...pagination
482
+ };
483
+ },
484
+ async create(values, signal) {
485
+ return request({
486
+ method: "POST",
487
+ url: "/authorization-resources",
488
+ body: buildCreateBody(values),
489
+ signal
490
+ });
491
+ },
492
+ async update(resourceId, values, signal) {
493
+ return request({
494
+ method: "PATCH",
495
+ url: `/authorization-resources/${encodeURIComponent(resourceId)}`,
496
+ body: buildUpdateBody(values),
497
+ signal
498
+ });
499
+ },
500
+ async remove(resourceId, signal) {
501
+ return request({
502
+ method: "DELETE",
503
+ url: `/authorization-resources/${encodeURIComponent(resourceId)}`,
504
+ signal
505
+ });
506
+ }
507
+ };
508
+ }
509
+ function createDefaultResourceRequest(options = {}) {
510
+ const baseUrl = (options.baseUrl ?? "").replace(/\/+$/, "");
511
+ const credentials = options.credentials ?? "include";
512
+ return async function request({
513
+ method,
514
+ url,
515
+ body,
516
+ signal
517
+ }) {
518
+ const path = url.startsWith("http") ? url : `${baseUrl}${url.startsWith("/") ? url : `/${url}`}`;
519
+ const headers = {
520
+ Accept: "application/json",
521
+ ...await options.getHeaders?.()
522
+ };
523
+ let payload;
524
+ if (body !== void 0) {
525
+ headers["Content-Type"] = "application/json";
526
+ payload = JSON.stringify(body);
527
+ }
528
+ const response = await fetch(path, {
529
+ method,
530
+ headers,
531
+ body: payload,
532
+ credentials,
533
+ signal
534
+ });
535
+ if (!response.ok) {
536
+ const text2 = await response.text().catch(() => "");
537
+ throw new Error(text2 || `HTTP ${response.status}`);
538
+ }
539
+ if (response.status === 204) {
540
+ return void 0;
541
+ }
542
+ const text = await response.text();
543
+ if (!text) return void 0;
544
+ return JSON.parse(text);
545
+ };
546
+ }
547
+
548
+ // src/react/ResourceManager.tsx
164
549
  var import_jsx_runtime2 = require("react/jsx-runtime");
165
550
  var PAGE_SIZE_OPTIONS = ["10", "20", "50", "100"];
166
551
  var emptyForm = () => ({
167
552
  name: "",
168
553
  identification: "",
169
- status: import_resources.RESOURCE_STATUS_ENABLED,
554
+ status: RESOURCE_STATUS_ENABLED,
170
555
  private: false,
171
556
  description: ""
172
557
  });
@@ -271,7 +656,7 @@ function ResourceManager({
271
656
  }
272
657
  };
273
658
  const statusLabel = (0, import_react2.useMemo)(
274
- () => Object.fromEntries(import_resources.RESOURCE_STATUS_OPTIONS.map((o) => [o.value, o.label])),
659
+ () => Object.fromEntries(RESOURCE_STATUS_OPTIONS.map((o) => [o.value, o.label])),
275
660
  []
276
661
  );
277
662
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.root, children: [
@@ -300,8 +685,8 @@ function ResourceManager({
300
685
  {
301
686
  style: {
302
687
  ...styles.badge,
303
- background: row.status === import_resources.RESOURCE_STATUS_ENABLED ? "#e8f7ef" : "#f4f4f5",
304
- color: row.status === import_resources.RESOURCE_STATUS_ENABLED ? "#067647" : "#667085"
688
+ background: row.status === RESOURCE_STATUS_ENABLED ? "#e8f7ef" : "#f4f4f5",
689
+ color: row.status === RESOURCE_STATUS_ENABLED ? "#067647" : "#667085"
305
690
  },
306
691
  children: statusLabel[row.status] ?? row.status
307
692
  }
@@ -409,7 +794,7 @@ function ResourceManager({
409
794
  ...prev,
410
795
  status: Number(e.target.value)
411
796
  })),
412
- children: import_resources.RESOURCE_STATUS_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
797
+ children: RESOURCE_STATUS_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
413
798
  }
414
799
  )
415
800
  ] }),
@@ -421,7 +806,7 @@ function ResourceManager({
421
806
  style: styles.input,
422
807
  value: form.private ? "1" : "0",
423
808
  onChange: (e) => setForm((prev) => ({ ...prev, private: e.target.value === "1" })),
424
- children: import_resources.RESOURCE_PRIVATE_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: opt.value ? "1" : "0", children: opt.label }, String(opt.value)))
809
+ children: RESOURCE_PRIVATE_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: opt.value ? "1" : "0", children: opt.label }, String(opt.value)))
425
810
  }
426
811
  )
427
812
  ] }),
@@ -603,11 +988,6 @@ var styles = {
603
988
  paddingTop: 4
604
989
  }
605
990
  };
606
-
607
- // src/react/index.ts
608
- var import_core2 = require("../core");
609
- var import_snowflake = require("../utils/snowflake");
610
- var import_resources2 = require("../resources");
611
991
  // Annotate the CommonJS export names for ESM import in node:
612
992
  0 && (module.exports = {
613
993
  Can,