locator-ars-lib 1.0.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/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # Locator ARS Lib
2
+
3
+ Библиотека для управления проверкой прав доступа во Vue 3 приложениях.
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ npm install locator-ars-lib
9
+ ```
10
+
11
+ или
12
+
13
+ ```bash
14
+ yarn add locator-ars-lib
15
+ ```
16
+
17
+ ## Настройка
18
+
19
+ Добавьте плагин в ваше Vue приложение:
20
+
21
+ ```typescript
22
+ // main.ts
23
+ import { createApp } from "vue";
24
+ import App from "./App.vue";
25
+ import LocatorArsLib from "locator-ars-lib";
26
+
27
+ const app = createApp(App);
28
+
29
+ // Установка с настройками (опционально)
30
+ app.use(LocatorArsLib, {
31
+ baseUrl: "https://your-api.com", // Базовый URL для API (опционально)
32
+ endpoint: "/api/v1/dashboard/access", // Путь для проверки прав (опционально)
33
+ });
34
+
35
+ app.mount("#app");
36
+ ```
37
+
38
+ ## Использование
39
+
40
+ ### 1. Компонент Check
41
+
42
+ Оберните компоненты, требующие проверку прав:
43
+
44
+ ```vue
45
+ <template>
46
+ <Check action="edit_user">
47
+ <button>Edit User</button>
48
+ </Check>
49
+ </template>
50
+ ```
51
+
52
+ С фолбэком при отсутствии прав:
53
+
54
+ ```vue
55
+ <template>
56
+ <Check action="delete_item">
57
+ <button>Delete</button>
58
+
59
+ <template #fallback>
60
+ <span>You don't have permission to delete</span>
61
+ </template>
62
+ </Check>
63
+ </template>
64
+ ```
65
+
66
+ С отображением загрузки:
67
+
68
+ ```vue
69
+ <template>
70
+ <Check action="create_project">
71
+ <button>Create Project</button>
72
+
73
+ <template #loading>
74
+ <span>Checking permissions...</span>
75
+ </template>
76
+
77
+ <template #fallback>
78
+ <span>No permission</span>
79
+ </template>
80
+ </Check>
81
+ </template>
82
+ ```
83
+
84
+ ### 2. Директива v-can
85
+
86
+ Используйте директиву для простой проверки на элементах:
87
+
88
+ ```vue
89
+ <template>
90
+ <button v-can="'edit_user'">Edit User</button>
91
+
92
+ <div v-can="'view_analytics'">
93
+ <!-- Аналитика будет видна только при наличии прав -->
94
+ </div>
95
+ </template>
96
+ ```
97
+
98
+ ### 3. Композиция usePermissions
99
+
100
+ Для программного использования в компонентах:
101
+
102
+ ```vue
103
+ <script setup>
104
+ import { usePermissions } from "locator-ars-lib";
105
+
106
+ // Проверка одного права
107
+ const { can, isLoading } = usePermissions("manage_users");
108
+
109
+ // Проверка с реактивным действием
110
+ const action = ref("edit_post");
111
+ const { can: canEditPost } = usePermissions(action);
112
+
113
+ // Ручная проверка
114
+ const { check, can: canDeleteUser } = usePermissions("delete_user", {
115
+ autoCheck: false,
116
+ });
117
+
118
+ // Проверить вручную при необходимости
119
+ function handleDelete() {
120
+ check().then(() => {
121
+ if (canDeleteUser.value) {
122
+ // Выполнить удаление
123
+ }
124
+ });
125
+ }
126
+ </script>
127
+ ```
128
+
129
+ ## API
130
+
131
+ ### Компонент Check
132
+
133
+ | Prop | Тип | По умолчанию | Описание |
134
+ | -------- | ------- | ------------ | ----------------------------------------------- |
135
+ | action | string | | Название действия для проверки |
136
+ | fallback | boolean | false | Показывать ли fallback слот при отсутствии прав |
137
+
138
+ ### Директива v-can
139
+
140
+ ```vue
141
+ v-can="'action_name'"
142
+ ```
143
+
144
+ ### usePermissions composable
145
+
146
+ ```typescript
147
+ const {
148
+ can, // Computed<boolean> - есть ли права
149
+ isLoading, // Ref<boolean> - выполняется ли загрузка
150
+ error, // Ref<Error | null> - ошибка, если есть
151
+ check, // () => Promise<void> - функция для ручной проверки
152
+ } = usePermissions(action, options);
153
+ ```
154
+
155
+ | Параметр | Тип | По умолчанию | Описание |
156
+ | ----------------- | --------------------- | ------------ | ------------------------------------ |
157
+ | action | string \| Ref<string> | | Название действия для проверки |
158
+ | options.autoCheck | boolean | true | Автоматически проверять при создании |
159
+
160
+ ## Лицензия
161
+
162
+ MIT
@@ -0,0 +1,132 @@
1
+ var h = Object.defineProperty;
2
+ var y = (e, s, n) => s in e ? h(e, s, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[s] = n;
3
+ var o = (e, s, n) => (y(e, typeof s != "symbol" ? s + "" : s, n), n);
4
+ import { inject as v, ref as l, computed as m, watch as _, onUnmounted as g, defineComponent as k, toRefs as w, renderSlot as u } from "vue";
5
+ import P from "axios";
6
+ class S {
7
+ constructor(s) {
8
+ o(this, "axios");
9
+ o(this, "cache", /* @__PURE__ */ new Map());
10
+ o(this, "endpoint");
11
+ this.axios = P.create({
12
+ baseURL: (s == null ? void 0 : s.baseUrl) || ""
13
+ }), this.endpoint = (s == null ? void 0 : s.endpoint) || "/api/v1/dashboard/access";
14
+ }
15
+ async can(s) {
16
+ if (this.cache.has(s))
17
+ return this.cache.get(s);
18
+ try {
19
+ const a = (await this.axios.get(this.endpoint, {
20
+ params: { action: s }
21
+ })).data.allowed || !1;
22
+ return this.cache.set(s, a), a;
23
+ } catch (n) {
24
+ return console.error(`Error checking permission for ${s}:`, n), !1;
25
+ }
26
+ }
27
+ clearCache(s) {
28
+ s ? this.cache.delete(s) : this.cache.clear();
29
+ }
30
+ }
31
+ const f = Symbol("Permissions");
32
+ function C(e) {
33
+ return new S(e);
34
+ }
35
+ const $ = {
36
+ install(e, s) {
37
+ const n = C(s);
38
+ e.provide(f, n);
39
+ }
40
+ };
41
+ function d() {
42
+ const e = v(f);
43
+ if (!e)
44
+ throw new Error("Permissions plugin not installed!");
45
+ return e;
46
+ }
47
+ function D(e, s = {}) {
48
+ const n = d(), a = l(null), i = l(!1), t = l(null), p = m(() => typeof e == "string" ? e : e.value), c = async () => {
49
+ if (p.value) {
50
+ i.value = !0, t.value = null;
51
+ try {
52
+ a.value = await n.can(p.value);
53
+ } catch (r) {
54
+ t.value = r instanceof Error ? r : new Error(String(r)), a.value = !1;
55
+ } finally {
56
+ i.value = !1;
57
+ }
58
+ }
59
+ };
60
+ if (s.autoCheck !== !1 && c(), typeof e != "string") {
61
+ const r = _(e, () => {
62
+ c();
63
+ });
64
+ g(() => {
65
+ r();
66
+ });
67
+ }
68
+ return {
69
+ isAllowed: a,
70
+ isLoading: i,
71
+ error: t,
72
+ check: c,
73
+ can: m(() => a.value === !0)
74
+ };
75
+ }
76
+ const E = k({
77
+ name: "Check",
78
+ props: {
79
+ action: {
80
+ type: String,
81
+ required: !0
82
+ },
83
+ fallback: {
84
+ type: Boolean,
85
+ default: !1
86
+ }
87
+ },
88
+ setup(e) {
89
+ const { action: s } = w(e), { can: n, isLoading: a } = D(s, { autoCheck: !0 });
90
+ return {
91
+ can: n,
92
+ isLoading: a
93
+ };
94
+ }
95
+ }), L = (e, s) => {
96
+ const n = e.__vccOpts || e;
97
+ for (const [a, i] of s)
98
+ n[a] = i;
99
+ return n;
100
+ };
101
+ function b(e, s, n, a, i, t) {
102
+ return e.can ? u(e.$slots, "default", { key: 0 }) : e.isLoading ? u(e.$slots, "loading", { key: 2 }) : u(e.$slots, "fallback", { key: 1 });
103
+ }
104
+ const x = /* @__PURE__ */ L(E, [["render", b]]), U = {
105
+ async mounted(e, s) {
106
+ const n = e.style.display;
107
+ e._permission_data = {
108
+ action: s.value,
109
+ originalDisplay: n
110
+ }, await d().can(s.value) || (e.style.display = "none");
111
+ },
112
+ async updated(e, s) {
113
+ (!e._permission_data || e._permission_data.action !== s.value) && (e._permission_data = {
114
+ action: s.value,
115
+ originalDisplay: e.style.display || ""
116
+ }, await d().can(s.value) ? e.style.display = e._permission_data.originalDisplay : e.style.display = "none");
117
+ },
118
+ unmounted(e) {
119
+ e._permission_data && (e.style.display = e._permission_data.originalDisplay, delete e._permission_data);
120
+ }
121
+ }, A = {
122
+ install(e, s) {
123
+ e.use($, s), e.component("Check", x), e.directive("can", U);
124
+ }
125
+ };
126
+ export {
127
+ x as Check,
128
+ A as default,
129
+ C as setupPermissions,
130
+ D as usePermissions,
131
+ U as vCan
132
+ };
@@ -0,0 +1 @@
1
+ (function(n,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("vue"),require("axios")):typeof define=="function"&&define.amd?define(["exports","vue","axios"],i):(n=typeof globalThis<"u"?globalThis:n||self,i(n.LocatorArsLib={},n.Vue,n.axios))})(this,function(n,i,t){"use strict";var $=Object.defineProperty;var L=(n,i,t)=>i in n?$(n,i,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[i]=t;var u=(n,i,t)=>(L(n,typeof i!="symbol"?i+"":i,t),t);class P{constructor(s){u(this,"axios");u(this,"cache",new Map);u(this,"endpoint");this.axios=t.create({baseURL:(s==null?void 0:s.baseUrl)||""}),this.endpoint=(s==null?void 0:s.endpoint)||"/api/v1/dashboard/access"}async can(s){if(this.cache.has(s))return this.cache.get(s);try{const a=(await this.axios.get(this.endpoint,{params:{action:s}})).data.allowed||!1;return this.cache.set(s,a),a}catch(r){return console.error(`Error checking permission for ${s}:`,r),!1}}clearCache(s){s?this.cache.delete(s):this.cache.clear()}}const p=Symbol("Permissions");function m(e){return new P(e)}const S={install(e,s){const r=m(s);e.provide(p,r)}};function d(){const e=i.inject(p);if(!e)throw new Error("Permissions plugin not installed!");return e}function h(e,s={}){const r=d(),a=i.ref(null),o=i.ref(!1),l=i.ref(null),_=i.computed(()=>typeof e=="string"?e:e.value),f=async()=>{if(_.value){o.value=!0,l.value=null;try{a.value=await r.can(_.value)}catch(c){l.value=c instanceof Error?c:new Error(String(c)),a.value=!1}finally{o.value=!1}}};if(s.autoCheck!==!1&&f(),typeof e!="string"){const c=i.watch(e,()=>{f()});i.onUnmounted(()=>{c()})}return{isAllowed:a,isLoading:o,error:l,check:f,can:i.computed(()=>a.value===!0)}}const g=i.defineComponent({name:"Check",props:{action:{type:String,required:!0},fallback:{type:Boolean,default:!1}},setup(e){const{action:s}=i.toRefs(e),{can:r,isLoading:a}=h(s,{autoCheck:!0});return{can:r,isLoading:a}}}),k=(e,s)=>{const r=e.__vccOpts||e;for(const[a,o]of s)r[a]=o;return r};function w(e,s,r,a,o,l){return e.can?i.renderSlot(e.$slots,"default",{key:0}):e.isLoading?i.renderSlot(e.$slots,"loading",{key:2}):i.renderSlot(e.$slots,"fallback",{key:1})}const y=k(g,[["render",w]]),v={async mounted(e,s){const r=e.style.display;e._permission_data={action:s.value,originalDisplay:r},await d().can(s.value)||(e.style.display="none")},async updated(e,s){(!e._permission_data||e._permission_data.action!==s.value)&&(e._permission_data={action:s.value,originalDisplay:e.style.display||""},await d().can(s.value)?e.style.display=e._permission_data.originalDisplay:e.style.display="none")},unmounted(e){e._permission_data&&(e.style.display=e._permission_data.originalDisplay,delete e._permission_data)}},C={install(e,s){e.use(S,s),e.component("Check",y),e.directive("can",v)}};n.Check=y,n.default=C,n.setupPermissions=m,n.usePermissions=h,n.vCan=v,Object.defineProperties(n,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div class="example-container">
3
+ <h1>Permissions Examples</h1>
4
+
5
+ <!-- Пример использования компонента Check -->
6
+ <div class="example-block">
7
+ <h2>Using Check Component</h2>
8
+
9
+ <Check action="view_dashboard">
10
+ <div class="dashboard-panel">
11
+ <h3>Dashboard Content</h3>
12
+ <p>This content is only visible for users with 'view_dashboard' permission</p>
13
+ </div>
14
+
15
+ <template #fallback>
16
+ <div class="no-permission">
17
+ <p>You don't have permission to view the dashboard</p>
18
+ </div>
19
+ </template>
20
+
21
+ <template #loading>
22
+ <div class="loading">
23
+ <p>Loading permissions...</p>
24
+ </div>
25
+ </template>
26
+ </Check>
27
+ </div>
28
+
29
+ <!-- Пример использования директивы v-can -->
30
+ <div class="example-block">
31
+ <h2>Using v-can Directive</h2>
32
+
33
+ <button v-can="'edit_user'" class="action-button">
34
+ Edit User
35
+ </button>
36
+
37
+ <button v-can="'delete_user'" class="action-button danger">
38
+ Delete User
39
+ </button>
40
+ </div>
41
+
42
+ <!-- Пример использования composable usePermissions -->
43
+ <div class="example-block">
44
+ <h2>Using usePermissions Composable</h2>
45
+
46
+ <div class="permission-status">
47
+ <p>Can create project: <span :class="canCreateProject ? 'allowed' : 'denied'">{{ canCreateProject ? 'Yes' : 'No' }}</span></p>
48
+ <p>Is loading: {{ isLoadingPermission ? 'Yes' : 'No' }}</p>
49
+ <button @click="checkPermissionManually" class="action-button">
50
+ Check Again
51
+ </button>
52
+ </div>
53
+
54
+ <div v-if="canCreateProject" class="action-panel">
55
+ <button class="action-button primary">Create New Project</button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import { Check, usePermissions } from 'locator-ars-lib'
63
+
64
+ // Использование composable
65
+ const { can: canCreateProject, isLoading: isLoadingPermission, check: checkPermissionManually } = usePermissions('create_project')
66
+ </script>
67
+
68
+ <style scoped>
69
+ .example-container {
70
+ max-width: 800px;
71
+ margin: 0 auto;
72
+ padding: 20px;
73
+ font-family: Arial, sans-serif;
74
+ }
75
+
76
+ .example-block {
77
+ margin-bottom: 30px;
78
+ padding: 20px;
79
+ border: 1px solid #eee;
80
+ border-radius: 5px;
81
+ }
82
+
83
+ .dashboard-panel {
84
+ background-color: #f5f5f5;
85
+ padding: 15px;
86
+ border-radius: 4px;
87
+ }
88
+
89
+ .action-button {
90
+ padding: 8px 16px;
91
+ margin-right: 10px;
92
+ border: none;
93
+ border-radius: 4px;
94
+ background-color: #4a90e2;
95
+ color: white;
96
+ cursor: pointer;
97
+ }
98
+
99
+ .action-button.danger {
100
+ background-color: #e25c4a;
101
+ }
102
+
103
+ .action-button.primary {
104
+ background-color: #42b983;
105
+ }
106
+
107
+ .permission-status {
108
+ margin-bottom: 15px;
109
+ }
110
+
111
+ .allowed {
112
+ color: #42b983;
113
+ font-weight: bold;
114
+ }
115
+
116
+ .denied {
117
+ color: #e25c4a;
118
+ font-weight: bold;
119
+ }
120
+
121
+ .no-permission {
122
+ padding: 15px;
123
+ background-color: #ffeeee;
124
+ border-left: 4px solid #e25c4a;
125
+ }
126
+
127
+ .loading {
128
+ padding: 15px;
129
+ background-color: #eeeeff;
130
+ border-left: 4px solid #4a90e2;
131
+ }
132
+ </style>
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "locator-ars-lib",
3
+ "version": "1.0.0",
4
+ "description": "Permissions library for Vue 3 applications",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "vue-tsc && vite build",
9
+ "test": "vitest run",
10
+ "lint": "eslint src --ext .ts,.vue"
11
+ },
12
+ "keywords": [
13
+ "vue",
14
+ "permissions",
15
+ "access control",
16
+ "vue3"
17
+ ],
18
+ "author": "badkiko",
19
+ "license": "MIT",
20
+ "peerDependencies": {
21
+ "axios": "^0.27.0",
22
+ "vue": "^3.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^18.19.100",
26
+ "@vitejs/plugin-vue": "^4.0.0",
27
+ "@vue/runtime-core": "^3.5.13",
28
+ "axios": "^0.27.2",
29
+ "typescript": "^4.9.3",
30
+ "vite": "^4.0.0",
31
+ "vue": "^3.2.45",
32
+ "vue-tsc": "^1.0.9"
33
+ }
34
+ }
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <slot v-if="can" />
3
+ <slot name="fallback" v-else-if="!isLoading" />
4
+ <slot name="loading" v-else />
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ import { defineComponent, toRefs, PropType } from 'vue';
9
+ import { usePermissions } from '../composables/usePermissions';
10
+
11
+ export default defineComponent({
12
+ name: 'Check',
13
+ props: {
14
+ action: {
15
+ type: String as PropType<string>,
16
+ required: true
17
+ },
18
+ fallback: {
19
+ type: Boolean,
20
+ default: false
21
+ }
22
+ },
23
+ setup(props) {
24
+ const { action } = toRefs(props);
25
+ const { can, isLoading } = usePermissions(action, { autoCheck: true });
26
+
27
+ return {
28
+ can,
29
+ isLoading
30
+ };
31
+ }
32
+ });
33
+ </script>
@@ -0,0 +1,57 @@
1
+ import { ref, computed, Ref, watch, onUnmounted } from 'vue'
2
+ import { usePermissionsService } from '../plugin'
3
+
4
+ export interface UsePermissionsOptions {
5
+ autoCheck?: boolean
6
+ }
7
+
8
+ export function usePermissions(action: string | Ref<string>, options: UsePermissionsOptions = {}) {
9
+ const permissionsService = usePermissionsService()
10
+ const isAllowed = ref<boolean | null>(null)
11
+ const isLoading = ref(false)
12
+ const error = ref<Error | null>(null)
13
+
14
+ const actionValue = computed(() => {
15
+ return typeof action === 'string' ? action : action.value
16
+ })
17
+
18
+ const checkPermission = async () => {
19
+ if (!actionValue.value) return
20
+
21
+ isLoading.value = true
22
+ error.value = null
23
+
24
+ try {
25
+ isAllowed.value = await permissionsService.can(actionValue.value)
26
+ } catch (err) {
27
+ error.value = err instanceof Error ? err : new Error(String(err))
28
+ isAllowed.value = false
29
+ } finally {
30
+ isLoading.value = false
31
+ }
32
+ }
33
+
34
+ // Auto-check on mount if requested
35
+ if (options.autoCheck !== false) {
36
+ checkPermission()
37
+ }
38
+
39
+ // Re-check when action changes
40
+ if (typeof action !== 'string') {
41
+ const unwatch = watch(action, () => {
42
+ checkPermission()
43
+ })
44
+
45
+ onUnmounted(() => {
46
+ unwatch()
47
+ })
48
+ }
49
+
50
+ return {
51
+ isAllowed,
52
+ isLoading,
53
+ error,
54
+ check: checkPermission,
55
+ can: computed(() => isAllowed.value === true)
56
+ }
57
+ }
@@ -0,0 +1,55 @@
1
+ import { ObjectDirective, DirectiveBinding } from 'vue'
2
+ import { usePermissionsService } from '../plugin'
3
+
4
+ interface CanHTMLElement extends HTMLElement {
5
+ _permission_data?: {
6
+ action: string
7
+ originalDisplay: string
8
+ }
9
+ }
10
+
11
+ export const vCan: ObjectDirective = {
12
+ async mounted(el: CanHTMLElement, binding: DirectiveBinding) {
13
+ // Store original display value
14
+ const originalDisplay = el.style.display
15
+
16
+ // Store action and original display for updates
17
+ el._permission_data = {
18
+ action: binding.value,
19
+ originalDisplay
20
+ }
21
+
22
+ const permissionsService = usePermissionsService()
23
+ const hasPermission = await permissionsService.can(binding.value)
24
+
25
+ if (!hasPermission) {
26
+ el.style.display = 'none'
27
+ }
28
+ },
29
+
30
+ async updated(el: CanHTMLElement, binding: DirectiveBinding) {
31
+ if (!el._permission_data || el._permission_data.action !== binding.value) {
32
+ // Action has changed or directive is new
33
+ el._permission_data = {
34
+ action: binding.value,
35
+ originalDisplay: el.style.display || ''
36
+ }
37
+
38
+ const permissionsService = usePermissionsService()
39
+ const hasPermission = await permissionsService.can(binding.value)
40
+
41
+ if (!hasPermission) {
42
+ el.style.display = 'none'
43
+ } else {
44
+ el.style.display = el._permission_data.originalDisplay
45
+ }
46
+ }
47
+ },
48
+
49
+ unmounted(el: CanHTMLElement) {
50
+ if (el._permission_data) {
51
+ el.style.display = el._permission_data.originalDisplay
52
+ delete el._permission_data
53
+ }
54
+ }
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { App } from 'vue'
2
+ import Check from './components/Check.vue'
3
+ import { usePermissions } from './composables/usePermissions'
4
+ import { PermissionsPlugin, setupPermissions } from './plugin'
5
+ import { vCan } from './directives/vCan'
6
+
7
+ export {
8
+ Check,
9
+ usePermissions,
10
+ setupPermissions,
11
+ vCan
12
+ }
13
+
14
+ export default {
15
+ install(app: App, options?: { baseUrl?: string }) {
16
+ app.use(PermissionsPlugin, options)
17
+ app.component('Check', Check)
18
+ app.directive('can', vCan)
19
+ }
20
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { App, inject, InjectionKey } from 'vue'
2
+ import { PermissionsService, PermissionsOptions } from './services/permissionsService'
3
+
4
+ export const PermissionsKey: InjectionKey<PermissionsService> = Symbol('Permissions')
5
+
6
+ export function setupPermissions(options?: PermissionsOptions): PermissionsService {
7
+ return new PermissionsService(options)
8
+ }
9
+
10
+ export const PermissionsPlugin = {
11
+ install(app: App, options?: PermissionsOptions) {
12
+ const permissionsService = setupPermissions(options)
13
+ app.provide(PermissionsKey, permissionsService)
14
+ }
15
+ }
16
+
17
+ export function usePermissionsService(): PermissionsService {
18
+ const permissionsService = inject(PermissionsKey)
19
+ if (!permissionsService) {
20
+ throw new Error('Permissions plugin not installed!')
21
+ }
22
+ return permissionsService
23
+ }
@@ -0,0 +1,50 @@
1
+ import axios, { AxiosInstance } from 'axios'
2
+
3
+ export interface PermissionsOptions {
4
+ baseUrl?: string
5
+ endpoint?: string
6
+ }
7
+
8
+ export class PermissionsService {
9
+ private axios: AxiosInstance
10
+ private cache: Map<string, boolean> = new Map()
11
+ private endpoint: string
12
+
13
+ constructor(options?: PermissionsOptions) {
14
+ this.axios = axios.create({
15
+ baseURL: options?.baseUrl || ''
16
+ })
17
+ this.endpoint = options?.endpoint || '/api/v1/dashboard/access'
18
+ }
19
+
20
+ async can(action: string): Promise<boolean> {
21
+ // Check if we have a cached result
22
+ if (this.cache.has(action)) {
23
+ return this.cache.get(action) as boolean
24
+ }
25
+
26
+ try {
27
+ const response = await this.axios.get(this.endpoint, {
28
+ params: { action }
29
+ })
30
+
31
+ const allowed = response.data.allowed || false
32
+
33
+ // Cache the result
34
+ this.cache.set(action, allowed)
35
+
36
+ return allowed
37
+ } catch (error) {
38
+ console.error(`Error checking permission for ${action}:`, error)
39
+ return false
40
+ }
41
+ }
42
+
43
+ clearCache(action?: string): void {
44
+ if (action) {
45
+ this.cache.delete(action)
46
+ } else {
47
+ this.cache.clear()
48
+ }
49
+ }
50
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "node",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "preserve",
16
+ "esModuleInterop": true,
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+
24
+ /* Type declarations */
25
+ "declaration": true,
26
+ "declarationDir": "dist",
27
+ "outDir": "dist"
28
+ },
29
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
30
+ "references": [{ "path": "./tsconfig.node.json" }]
31
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import { resolve } from 'path'
4
+
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ build: {
8
+ lib: {
9
+ entry: resolve(__dirname, 'src/index.ts'),
10
+ name: 'LocatorArsLib',
11
+ fileName: (format) => `locator-ars-lib.${format}.js`
12
+ },
13
+ rollupOptions: {
14
+ external: ['vue', 'axios'],
15
+ output: {
16
+ globals: {
17
+ vue: 'Vue',
18
+ axios: 'axios'
19
+ }
20
+ }
21
+ }
22
+ }
23
+ })