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.
@@ -10,9 +10,112 @@ import {
10
10
  useMemo,
11
11
  useState
12
12
  } from "react";
13
- import {
14
- createPermissionStore
15
- } from "../core";
13
+
14
+ // src/core/store.ts
15
+ var DEFAULT_STATE = {
16
+ permissions: [],
17
+ roles: [],
18
+ isSuperAdmin: false
19
+ };
20
+ function toArray(value) {
21
+ return Array.isArray(value) ? value : [value];
22
+ }
23
+ function matchCodes(owned, required, mode, isSuperAdmin) {
24
+ if (isSuperAdmin) return true;
25
+ if (required.length === 0) return true;
26
+ if (mode === "all") {
27
+ return required.every((code) => owned.has(code));
28
+ }
29
+ return required.some((code) => owned.has(code));
30
+ }
31
+ var PermissionStore = class {
32
+ constructor(initial) {
33
+ this.listeners = /* @__PURE__ */ new Set();
34
+ this.state = {
35
+ ...DEFAULT_STATE,
36
+ ...initial,
37
+ permissions: initial?.permissions ?? [],
38
+ roles: initial?.roles ?? []
39
+ };
40
+ this.permissionSet = new Set(this.state.permissions);
41
+ this.roleSet = new Set(this.state.roles);
42
+ }
43
+ getState() {
44
+ return {
45
+ permissions: [...this.state.permissions],
46
+ roles: [...this.state.roles],
47
+ isSuperAdmin: this.state.isSuperAdmin
48
+ };
49
+ }
50
+ setState(next) {
51
+ this.state = {
52
+ permissions: next.permissions ?? this.state.permissions,
53
+ roles: next.roles ?? this.state.roles,
54
+ isSuperAdmin: next.isSuperAdmin ?? this.state.isSuperAdmin
55
+ };
56
+ this.permissionSet = new Set(this.state.permissions);
57
+ this.roleSet = new Set(this.state.roles);
58
+ this.emit();
59
+ }
60
+ setPermissions(permissions) {
61
+ this.setState({ permissions });
62
+ }
63
+ setRoles(roles) {
64
+ this.setState({ roles });
65
+ }
66
+ setSuperAdmin(isSuperAdmin) {
67
+ this.setState({ isSuperAdmin });
68
+ }
69
+ /** Replace permissions, roles and super-admin flag in one shot (typical after login). */
70
+ hydrate(payload) {
71
+ this.setState({
72
+ permissions: payload.permissions ?? [],
73
+ roles: payload.roles ?? [],
74
+ isSuperAdmin: payload.isSuperAdmin ?? false
75
+ });
76
+ }
77
+ /** Clear all authz data (typical after logout). */
78
+ clear() {
79
+ this.hydrate({});
80
+ }
81
+ hasPermission(codes, options = {}) {
82
+ const mode = options.mode ?? "any";
83
+ return matchCodes(
84
+ this.permissionSet,
85
+ toArray(codes).filter(Boolean),
86
+ mode,
87
+ Boolean(this.state.isSuperAdmin)
88
+ );
89
+ }
90
+ hasRole(codes, options = {}) {
91
+ const mode = options.mode ?? "any";
92
+ return matchCodes(
93
+ this.roleSet,
94
+ toArray(codes).filter(Boolean),
95
+ mode,
96
+ Boolean(this.state.isSuperAdmin)
97
+ );
98
+ }
99
+ /** Alias of `hasPermission` for template-friendly APIs. */
100
+ can(codes, options) {
101
+ return this.hasPermission(codes, options);
102
+ }
103
+ subscribe(listener) {
104
+ this.listeners.add(listener);
105
+ return () => {
106
+ this.listeners.delete(listener);
107
+ };
108
+ }
109
+ emit() {
110
+ const snapshot = this.getState();
111
+ this.listeners.forEach((listener) => listener(snapshot));
112
+ }
113
+ };
114
+ function createPermissionStore(initial) {
115
+ return new PermissionStore(initial);
116
+ }
117
+
118
+ // src/react/context.tsx
16
119
  var PermissionContext = createContext(null);
17
120
  function PermissionProvider({
18
121
  children,
@@ -127,11 +230,287 @@ import {
127
230
  useMemo as useMemo2,
128
231
  useState as useState2
129
232
  } from "react";
130
- import {
131
- RESOURCE_PRIVATE_OPTIONS,
132
- RESOURCE_STATUS_ENABLED,
133
- RESOURCE_STATUS_OPTIONS
134
- } from "../resources";
233
+
234
+ // src/resources/types.ts
235
+ var RESOURCE_TYPE_API = "api";
236
+ var RESOURCE_STATUS_ENABLED = 1;
237
+ var RESOURCE_STATUS_DISABLED = 0;
238
+ var RESOURCE_STATUS_OPTIONS = [
239
+ { label: "\u542F\u7528", value: RESOURCE_STATUS_ENABLED },
240
+ { label: "\u7981\u7528", value: RESOURCE_STATUS_DISABLED }
241
+ ];
242
+ var RESOURCE_PRIVATE_OPTIONS = [
243
+ { label: "\u662F", value: true },
244
+ { label: "\u5426", value: false }
245
+ ];
246
+
247
+ // src/utils/snowflake.ts
248
+ var DEVICE_WORKER_ID_KEY = "hgams_device_worker_id";
249
+ function generateDeviceWorkerId() {
250
+ const cached = localStorage.getItem(DEVICE_WORKER_ID_KEY);
251
+ if (cached !== null && cached !== "") {
252
+ return BigInt(cached);
253
+ }
254
+ const components = [
255
+ navigator.userAgent,
256
+ screen.width + "x" + screen.height,
257
+ screen.colorDepth,
258
+ navigator.language,
259
+ (/* @__PURE__ */ new Date()).getTimezoneOffset(),
260
+ navigator.hardwareConcurrency,
261
+ navigator.deviceMemory
262
+ ];
263
+ const fingerprint = components.join("|");
264
+ let hash = 0;
265
+ for (let i = 0; i < fingerprint.length; i++) {
266
+ hash = (hash << 5) - hash + fingerprint.charCodeAt(i);
267
+ hash |= 0;
268
+ }
269
+ const maxWorkerId = Math.pow(2, 14) - 1;
270
+ const workerId = Math.abs(hash % maxWorkerId);
271
+ const workerIdBigInt = BigInt(workerId);
272
+ localStorage.setItem(DEVICE_WORKER_ID_KEY, workerIdBigInt.toString());
273
+ return workerIdBigInt;
274
+ }
275
+ var ConfigurableSnowflake = class {
276
+ constructor({
277
+ epoch = 0n,
278
+ workerId = 0n,
279
+ processId = 0n,
280
+ workerIdBits = 5,
281
+ processIdBits = 5,
282
+ sequenceBits = 12
283
+ } = {}) {
284
+ this.sequence = 0n;
285
+ this.latestTimestamp = BigInt(Date.now());
286
+ this.epoch = epoch;
287
+ const workerIdBitsBig = BigInt(workerIdBits);
288
+ const processIdBitsBig = BigInt(processIdBits);
289
+ const sequenceBitsBig = BigInt(sequenceBits);
290
+ const workerIdMask = -1n ^ -1n << workerIdBitsBig;
291
+ const processIdMask = -1n ^ -1n << processIdBitsBig;
292
+ this.sequenceMask = -1n ^ -1n << sequenceBitsBig;
293
+ this.timestampLeftShift = sequenceBitsBig + workerIdBitsBig + processIdBitsBig;
294
+ this.workerIdShift = sequenceBitsBig + processIdBitsBig;
295
+ this.processIdShift = sequenceBitsBig;
296
+ this.workerId = workerId & workerIdMask;
297
+ this.processId = processId & processIdMask;
298
+ }
299
+ nextId() {
300
+ const timestamp = BigInt(Date.now());
301
+ if (this.latestTimestamp === timestamp) {
302
+ this.sequence = this.sequence + 1n & this.sequenceMask;
303
+ } else {
304
+ this.sequence = 0n;
305
+ this.latestTimestamp = timestamp;
306
+ }
307
+ return timestamp - this.epoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.processId << this.processIdShift | this.sequence;
308
+ }
309
+ };
310
+ var snowyflake = new ConfigurableSnowflake({
311
+ workerId: generateDeviceWorkerId(),
312
+ workerIdBits: 14,
313
+ processIdBits: 0,
314
+ sequenceBits: 8,
315
+ epoch: BigInt((/* @__PURE__ */ new Date("2026-06-08")).getTime())
316
+ });
317
+ function getAppClientId() {
318
+ return snowyflake.nextId().toString();
319
+ }
320
+ function getDeviceWorkerId() {
321
+ return snowyflake.workerId.toString();
322
+ }
323
+
324
+ // src/resources/pagination.ts
325
+ function appendQueryParam(parts, key, value) {
326
+ if (value !== void 0 && value !== null && String(value) !== "") {
327
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
328
+ }
329
+ }
330
+ function extractPagination(payload) {
331
+ if (!payload || typeof payload !== "object") {
332
+ return {
333
+ pageToken: "",
334
+ hasPreviousPage: false,
335
+ hasNextPage: false
336
+ };
337
+ }
338
+ const envelope = payload;
339
+ const pagination = envelope.pagination && typeof envelope.pagination === "object" ? envelope.pagination : envelope.page && typeof envelope.page === "object" ? envelope.page : null;
340
+ const rawPageToken = pagination?.["page-token"] ?? pagination?.pageToken ?? pagination?.nextPageToken ?? envelope["page-token"] ?? envelope.pageToken ?? "";
341
+ const pageToken = rawPageToken === void 0 || rawPageToken === null ? "" : String(rawPageToken);
342
+ const hasPreviousPage = Boolean(
343
+ pagination?.hasPreviousPage ?? pagination?.hasPrevious ?? pagination?.hasPre ?? pagination?.previous ?? envelope.hasPreviousPage ?? envelope.hasPrevious
344
+ );
345
+ const hasNextPage = Boolean(
346
+ pagination?.hasNextPage ?? pagination?.hasNext ?? pagination?.next ?? envelope.hasNextPage ?? envelope.hasNext
347
+ );
348
+ return {
349
+ pageToken,
350
+ hasPreviousPage,
351
+ hasNextPage
352
+ };
353
+ }
354
+ function extractRecords(payload) {
355
+ if (!payload) return [];
356
+ if (Array.isArray(payload)) return payload;
357
+ if (typeof payload === "object") {
358
+ const envelope = payload;
359
+ const data = envelope.data;
360
+ if (Array.isArray(data)) return data;
361
+ if (data && typeof data === "object") {
362
+ const nested = data;
363
+ if (Array.isArray(nested.records)) return nested.records;
364
+ if (Array.isArray(nested.items)) return nested.items;
365
+ if (Array.isArray(nested.list)) return nested.list;
366
+ if (Array.isArray(nested.content)) return nested.content;
367
+ }
368
+ if (Array.isArray(envelope.records)) return envelope.records;
369
+ if (Array.isArray(envelope.items)) return envelope.items;
370
+ if (Array.isArray(envelope.list)) return envelope.list;
371
+ if (Array.isArray(envelope.content)) return envelope.content;
372
+ }
373
+ return [];
374
+ }
375
+
376
+ // src/resources/api.ts
377
+ function toResourceStatus(value) {
378
+ const n = Number(value);
379
+ return n === RESOURCE_STATUS_ENABLED ? RESOURCE_STATUS_ENABLED : RESOURCE_STATUS_DISABLED;
380
+ }
381
+ function toBoolean(value) {
382
+ if (typeof value === "boolean") return value;
383
+ if (typeof value === "number") return value !== 0;
384
+ if (typeof value === "string") {
385
+ const v = value.trim().toLowerCase();
386
+ return v === "true" || v === "1" || v === "yes" || v === "\u662F";
387
+ }
388
+ return false;
389
+ }
390
+ function mapAuthorizationResource(item) {
391
+ if (!item || typeof item !== "object") return null;
392
+ const row = item;
393
+ const resourceId = row.resourceId ?? row.resource_id ?? row.id;
394
+ if (resourceId === void 0 || resourceId === null || resourceId === "") return null;
395
+ return {
396
+ resourceId: String(resourceId),
397
+ type: RESOURCE_TYPE_API,
398
+ name: String(row.name ?? ""),
399
+ identification: String(row.identification ?? row.identity ?? ""),
400
+ status: toResourceStatus(row.status),
401
+ private: toBoolean(row.private ?? row.isPrivate ?? row.is_private),
402
+ description: String(row.description ?? row.desc ?? "")
403
+ };
404
+ }
405
+ function buildListUrl(params) {
406
+ const parts = [];
407
+ appendQueryParam(parts, "type", RESOURCE_TYPE_API);
408
+ appendQueryParam(parts, "page-token", params?.pagination?.pageToken ?? "");
409
+ appendQueryParam(parts, "page-size", params?.pagination?.pageSize ?? "20");
410
+ appendQueryParam(parts, "page-direction", params?.pagination?.pageDirection ?? "current");
411
+ return `/authorization-resources?${parts.join("&")}`;
412
+ }
413
+ function buildCreateBody(values, resourceId = getAppClientId()) {
414
+ return {
415
+ resourceId,
416
+ type: RESOURCE_TYPE_API,
417
+ name: values.name.trim(),
418
+ identification: values.identification.trim(),
419
+ status: values.status,
420
+ private: values.private,
421
+ description: values.description.trim()
422
+ };
423
+ }
424
+ function buildUpdateBody(values) {
425
+ return {
426
+ type: RESOURCE_TYPE_API,
427
+ name: values.name.trim(),
428
+ identification: values.identification.trim(),
429
+ status: values.status,
430
+ private: values.private,
431
+ description: values.description.trim()
432
+ };
433
+ }
434
+ function createAuthorizationResourceApi(request) {
435
+ return {
436
+ async list(params, signal) {
437
+ const json = await request({
438
+ method: "GET",
439
+ url: buildListUrl(params),
440
+ signal
441
+ });
442
+ const records = extractRecords(json).map(mapAuthorizationResource).filter((item) => item !== null);
443
+ const pagination = extractPagination(json);
444
+ return {
445
+ records,
446
+ ...pagination
447
+ };
448
+ },
449
+ async create(values, signal) {
450
+ return request({
451
+ method: "POST",
452
+ url: "/authorization-resources",
453
+ body: buildCreateBody(values),
454
+ signal
455
+ });
456
+ },
457
+ async update(resourceId, values, signal) {
458
+ return request({
459
+ method: "PATCH",
460
+ url: `/authorization-resources/${encodeURIComponent(resourceId)}`,
461
+ body: buildUpdateBody(values),
462
+ signal
463
+ });
464
+ },
465
+ async remove(resourceId, signal) {
466
+ return request({
467
+ method: "DELETE",
468
+ url: `/authorization-resources/${encodeURIComponent(resourceId)}`,
469
+ signal
470
+ });
471
+ }
472
+ };
473
+ }
474
+ function createDefaultResourceRequest(options = {}) {
475
+ const baseUrl = (options.baseUrl ?? "").replace(/\/+$/, "");
476
+ const credentials = options.credentials ?? "include";
477
+ return async function request({
478
+ method,
479
+ url,
480
+ body,
481
+ signal
482
+ }) {
483
+ const path = url.startsWith("http") ? url : `${baseUrl}${url.startsWith("/") ? url : `/${url}`}`;
484
+ const headers = {
485
+ Accept: "application/json",
486
+ ...await options.getHeaders?.()
487
+ };
488
+ let payload;
489
+ if (body !== void 0) {
490
+ headers["Content-Type"] = "application/json";
491
+ payload = JSON.stringify(body);
492
+ }
493
+ const response = await fetch(path, {
494
+ method,
495
+ headers,
496
+ body: payload,
497
+ credentials,
498
+ signal
499
+ });
500
+ if (!response.ok) {
501
+ const text2 = await response.text().catch(() => "");
502
+ throw new Error(text2 || `HTTP ${response.status}`);
503
+ }
504
+ if (response.status === 204) {
505
+ return void 0;
506
+ }
507
+ const text = await response.text();
508
+ if (!text) return void 0;
509
+ return JSON.parse(text);
510
+ };
511
+ }
512
+
513
+ // src/react/ResourceManager.tsx
135
514
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
136
515
  var PAGE_SIZE_OPTIONS = ["10", "20", "50", "100"];
137
516
  var emptyForm = () => ({
@@ -574,33 +953,14 @@ var styles = {
574
953
  paddingTop: 4
575
954
  }
576
955
  };
577
-
578
- // src/react/index.ts
579
- import { PermissionStore, createPermissionStore as createPermissionStore2 } from "../core";
580
- import { getAppClientId, getDeviceWorkerId, snowyflake } from "../utils/snowflake";
581
- import {
582
- RESOURCE_PRIVATE_OPTIONS as RESOURCE_PRIVATE_OPTIONS2,
583
- RESOURCE_STATUS_DISABLED,
584
- RESOURCE_STATUS_ENABLED as RESOURCE_STATUS_ENABLED2,
585
- RESOURCE_STATUS_OPTIONS as RESOURCE_STATUS_OPTIONS2,
586
- RESOURCE_TYPE_API,
587
- appendQueryParam,
588
- buildCreateBody,
589
- buildUpdateBody,
590
- createAuthorizationResourceApi,
591
- createDefaultResourceRequest,
592
- extractPagination,
593
- extractRecords,
594
- mapAuthorizationResource
595
- } from "../resources";
596
956
  export {
597
957
  Can,
598
958
  PermissionProvider,
599
959
  PermissionStore,
600
- RESOURCE_PRIVATE_OPTIONS2 as RESOURCE_PRIVATE_OPTIONS,
960
+ RESOURCE_PRIVATE_OPTIONS,
601
961
  RESOURCE_STATUS_DISABLED,
602
- RESOURCE_STATUS_ENABLED2 as RESOURCE_STATUS_ENABLED,
603
- RESOURCE_STATUS_OPTIONS2 as RESOURCE_STATUS_OPTIONS,
962
+ RESOURCE_STATUS_ENABLED,
963
+ RESOURCE_STATUS_OPTIONS,
604
964
  RESOURCE_TYPE_API,
605
965
  ResourceManager,
606
966
  appendQueryParam,
@@ -608,7 +968,7 @@ export {
608
968
  buildUpdateBody,
609
969
  createAuthorizationResourceApi,
610
970
  createDefaultResourceRequest,
611
- createPermissionStore2 as createPermissionStore,
971
+ createPermissionStore,
612
972
  extractPagination,
613
973
  extractRecords,
614
974
  getAppClientId,