@forge/react 11.2.9-next.0 → 11.3.0-experimental-6de6b19
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/CHANGELOG.md +45 -0
- package/out/hooks/__test__/usePermissions.test.d.ts +2 -0
- package/out/hooks/__test__/usePermissions.test.d.ts.map +1 -0
- package/out/hooks/__test__/usePermissions.test.js +384 -0
- package/out/hooks/usePermissions.d.ts +80 -0
- package/out/hooks/usePermissions.d.ts.map +1 -0
- package/out/hooks/usePermissions.js +177 -0
- package/out/hooks/useProductContext.d.ts +1 -1
- package/out/hooks/useProductContext.d.ts.map +1 -1
- package/out/index.d.ts +1 -0
- package/out/index.d.ts.map +1 -1
- package/out/index.js +3 -1
- package/package.json +10 -4
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @forge/react
|
|
2
2
|
|
|
3
|
+
## 11.3.0-experimental-6de6b19
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 01521ac: add usePermissions hook
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [01521ac]
|
|
12
|
+
- @forge/bridge@5.5.0-experimental-6de6b19
|
|
13
|
+
|
|
14
|
+
## 11.3.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 1fe70bc: Add package.json exports configuration for improved TypeScript module resolution in forge-react package.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 0371b03: Update type import from @forge/bridge
|
|
23
|
+
- Updated dependencies [8e77d28]
|
|
24
|
+
- Updated dependencies [185f844]
|
|
25
|
+
- @forge/bridge@5.5.0
|
|
26
|
+
|
|
27
|
+
## 11.3.0-next.1
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- 0371b03: Update type import from @forge/bridge
|
|
32
|
+
- Updated dependencies [8e77d28]
|
|
33
|
+
- @forge/bridge@5.5.0-next.0
|
|
34
|
+
|
|
35
|
+
## 11.3.0-next.0
|
|
36
|
+
|
|
37
|
+
### Minor Changes
|
|
38
|
+
|
|
39
|
+
- 1fe70bc: Add package.json exports configuration for improved TypeScript module resolution in forge-react package.
|
|
40
|
+
|
|
41
|
+
## 11.2.9
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- Updated dependencies [c51a29d]
|
|
46
|
+
- @forge/bridge@5.4.1
|
|
47
|
+
|
|
3
48
|
## 11.2.9-next.0
|
|
4
49
|
|
|
5
50
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePermissions.test.d.ts","sourceRoot":"","sources":["../../../src/hooks/__test__/usePermissions.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_hooks_1 = require("@testing-library/react-hooks");
|
|
4
|
+
const usePermissions_1 = require("../usePermissions");
|
|
5
|
+
const testUtils_1 = require("../../__test__/testUtils");
|
|
6
|
+
// Mock @forge/bridge
|
|
7
|
+
jest.mock('@forge/bridge', () => ({
|
|
8
|
+
view: {
|
|
9
|
+
getContext: jest.fn()
|
|
10
|
+
}
|
|
11
|
+
}));
|
|
12
|
+
const mockGetContext = jest.fn();
|
|
13
|
+
describe('usePermissions', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
(0, testUtils_1.setupBridge)();
|
|
17
|
+
// Set up the mock to use our mockGetContext function
|
|
18
|
+
const { view } = require('@forge/bridge');
|
|
19
|
+
view.getContext.mockImplementation(mockGetContext);
|
|
20
|
+
});
|
|
21
|
+
describe('Loading state', () => {
|
|
22
|
+
it('should start with loading true', () => {
|
|
23
|
+
const requiredPermissions = {
|
|
24
|
+
scopes: ['read:confluence-content']
|
|
25
|
+
};
|
|
26
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
27
|
+
expect(result.current.isLoading).toBe(true);
|
|
28
|
+
expect(result.current.hasPermission).toBe(false);
|
|
29
|
+
expect(result.current.missingPermissions).toBe(null);
|
|
30
|
+
expect(result.current.error).toBe(null);
|
|
31
|
+
});
|
|
32
|
+
it('should set loading to false after context loads', async () => {
|
|
33
|
+
const mockContext = {
|
|
34
|
+
permissions: {
|
|
35
|
+
scopes: ['read:confluence-content']
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
39
|
+
const requiredPermissions = {
|
|
40
|
+
scopes: ['read:confluence-content']
|
|
41
|
+
};
|
|
42
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
43
|
+
await (0, react_hooks_1.act)(async () => {
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
45
|
+
});
|
|
46
|
+
expect(result.current.isLoading).toBe(false);
|
|
47
|
+
expect(result.current.hasPermission).toBe(true);
|
|
48
|
+
expect(result.current.missingPermissions).toBe(null);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('Error handling', () => {
|
|
52
|
+
it('should handle context loading errors', async () => {
|
|
53
|
+
const error = new Error('Failed to load context');
|
|
54
|
+
mockGetContext.mockRejectedValue(error);
|
|
55
|
+
const requiredPermissions = {
|
|
56
|
+
scopes: ['read:confluence-content']
|
|
57
|
+
};
|
|
58
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
59
|
+
await (0, react_hooks_1.act)(async () => {
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
61
|
+
});
|
|
62
|
+
expect(result.current.isLoading).toBe(false);
|
|
63
|
+
expect(result.current.error).toEqual(error);
|
|
64
|
+
expect(result.current.hasPermission).toBe(false);
|
|
65
|
+
expect(result.current.missingPermissions).toBe(null);
|
|
66
|
+
});
|
|
67
|
+
it('should handle non-Error objects in catch block', async () => {
|
|
68
|
+
mockGetContext.mockRejectedValue('String error');
|
|
69
|
+
const requiredPermissions = {
|
|
70
|
+
scopes: ['read:confluence-content']
|
|
71
|
+
};
|
|
72
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
73
|
+
await (0, react_hooks_1.act)(async () => {
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
75
|
+
});
|
|
76
|
+
expect(result.current.error).toEqual(new Error('Failed to load context'));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('Scope permissions', () => {
|
|
80
|
+
it('should grant permission when all required scopes are present', async () => {
|
|
81
|
+
const mockContext = {
|
|
82
|
+
permissions: {
|
|
83
|
+
scopes: ['read:confluence-content', 'write:confluence-content']
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
87
|
+
const requiredPermissions = {
|
|
88
|
+
scopes: ['read:confluence-content']
|
|
89
|
+
};
|
|
90
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
91
|
+
await (0, react_hooks_1.act)(async () => {
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
93
|
+
});
|
|
94
|
+
expect(result.current.isLoading).toBe(false);
|
|
95
|
+
expect(result.current.hasPermission).toBe(true);
|
|
96
|
+
expect(result.current.missingPermissions).toBe(null);
|
|
97
|
+
});
|
|
98
|
+
it('should deny permission when required scopes are missing', async () => {
|
|
99
|
+
const mockContext = {
|
|
100
|
+
permissions: {
|
|
101
|
+
scopes: ['read:confluence-content']
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
105
|
+
const requiredPermissions = {
|
|
106
|
+
scopes: ['read:confluence-content', 'write:jira-work']
|
|
107
|
+
};
|
|
108
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
109
|
+
await (0, react_hooks_1.act)(async () => {
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
111
|
+
});
|
|
112
|
+
expect(result.current.isLoading).toBe(false);
|
|
113
|
+
expect(result.current.hasPermission).toBe(false);
|
|
114
|
+
expect(result.current.missingPermissions).toEqual({
|
|
115
|
+
scopes: ['write:jira-work']
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('should handle scopes as object format', async () => {
|
|
119
|
+
const mockContext = {
|
|
120
|
+
permissions: {
|
|
121
|
+
scopes: {
|
|
122
|
+
'read:confluence-content': { allowImpersonation: false },
|
|
123
|
+
'write:confluence-content': { allowImpersonation: true }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
128
|
+
const requiredPermissions = {
|
|
129
|
+
scopes: ['read:confluence-content']
|
|
130
|
+
};
|
|
131
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
132
|
+
await (0, react_hooks_1.act)(async () => {
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
134
|
+
});
|
|
135
|
+
expect(result.current.isLoading).toBe(false);
|
|
136
|
+
expect(result.current.hasPermission).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
it('should handle empty scopes array', async () => {
|
|
139
|
+
const mockContext = {
|
|
140
|
+
permissions: {
|
|
141
|
+
scopes: []
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
145
|
+
const requiredPermissions = {
|
|
146
|
+
scopes: []
|
|
147
|
+
};
|
|
148
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
149
|
+
await (0, react_hooks_1.act)(async () => {
|
|
150
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
151
|
+
});
|
|
152
|
+
expect(result.current.isLoading).toBe(false);
|
|
153
|
+
expect(result.current.hasPermission).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('External permissions', () => {
|
|
157
|
+
it('should grant permission for allowed fetch URLs', async () => {
|
|
158
|
+
const mockContext = {
|
|
159
|
+
permissions: {
|
|
160
|
+
external: {
|
|
161
|
+
fetch: {
|
|
162
|
+
backend: ['https://api.example.com', 'https://api.test.com'],
|
|
163
|
+
client: ['https://cdn.example.com']
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
169
|
+
const requiredPermissions = {
|
|
170
|
+
external: {
|
|
171
|
+
fetch: {
|
|
172
|
+
backend: ['https://api.example.com'],
|
|
173
|
+
client: ['https://cdn.example.com']
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
178
|
+
await (0, react_hooks_1.act)(async () => {
|
|
179
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
180
|
+
});
|
|
181
|
+
expect(result.current.isLoading).toBe(false);
|
|
182
|
+
expect(result.current.hasPermission).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
it('should deny permission for disallowed fetch URLs', async () => {
|
|
185
|
+
const mockContext = {
|
|
186
|
+
permissions: {
|
|
187
|
+
external: {
|
|
188
|
+
fetch: {
|
|
189
|
+
backend: ['https://api.example.com']
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
195
|
+
const requiredPermissions = {
|
|
196
|
+
external: {
|
|
197
|
+
fetch: {
|
|
198
|
+
backend: ['https://api.example.com', 'https://api.unauthorized.com']
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
203
|
+
await (0, react_hooks_1.act)(async () => {
|
|
204
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
205
|
+
});
|
|
206
|
+
expect(result.current.isLoading).toBe(false);
|
|
207
|
+
expect(result.current.hasPermission).toBe(false);
|
|
208
|
+
expect(result.current.missingPermissions).toEqual({
|
|
209
|
+
external: {
|
|
210
|
+
fetch: {
|
|
211
|
+
backend: ['https://api.unauthorized.com']
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
it('should handle wildcard URL patterns', async () => {
|
|
217
|
+
const mockContext = {
|
|
218
|
+
permissions: {
|
|
219
|
+
external: {
|
|
220
|
+
fetch: {
|
|
221
|
+
backend: ['https://api.example.com/*', 'https://*.test.com']
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
227
|
+
const requiredPermissions = {
|
|
228
|
+
external: {
|
|
229
|
+
fetch: {
|
|
230
|
+
backend: ['https://api.example.com/users', 'https://subdomain.test.com/api']
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
235
|
+
await (0, react_hooks_1.act)(async () => {
|
|
236
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
237
|
+
});
|
|
238
|
+
expect(result.current.isLoading).toBe(false);
|
|
239
|
+
expect(result.current.hasPermission).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
it('should handle resource permissions (fonts, images, etc.)', async () => {
|
|
242
|
+
const mockContext = {
|
|
243
|
+
permissions: {
|
|
244
|
+
external: {
|
|
245
|
+
fonts: ['https://fonts.googleapis.com'],
|
|
246
|
+
images: ['https://images.example.com'],
|
|
247
|
+
scripts: ['https://scripts.example.com']
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
252
|
+
const requiredPermissions = {
|
|
253
|
+
external: {
|
|
254
|
+
fonts: ['https://fonts.googleapis.com'],
|
|
255
|
+
images: ['https://images.example.com']
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
259
|
+
await (0, react_hooks_1.act)(async () => {
|
|
260
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
261
|
+
});
|
|
262
|
+
expect(result.current.isLoading).toBe(false);
|
|
263
|
+
expect(result.current.hasPermission).toBe(true);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
// Note: Content permissions are not supported in the current RuntimePermissions type
|
|
267
|
+
describe('Complex permission scenarios', () => {
|
|
268
|
+
it('should handle mixed permission types', async () => {
|
|
269
|
+
const mockContext = {
|
|
270
|
+
permissions: {
|
|
271
|
+
scopes: ['read:confluence-content'],
|
|
272
|
+
external: {
|
|
273
|
+
fetch: {
|
|
274
|
+
backend: ['https://api.example.com']
|
|
275
|
+
},
|
|
276
|
+
images: ['https://images.example.com']
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
281
|
+
const requiredPermissions = {
|
|
282
|
+
scopes: ['read:confluence-content'],
|
|
283
|
+
external: {
|
|
284
|
+
fetch: {
|
|
285
|
+
backend: ['https://api.example.com']
|
|
286
|
+
},
|
|
287
|
+
images: ['https://images.example.com']
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
291
|
+
await (0, react_hooks_1.act)(async () => {
|
|
292
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
293
|
+
});
|
|
294
|
+
expect(result.current.isLoading).toBe(false);
|
|
295
|
+
expect(result.current.hasPermission).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
it('should identify missing permissions across different types', async () => {
|
|
298
|
+
const mockContext = {
|
|
299
|
+
permissions: {
|
|
300
|
+
scopes: ['read:confluence-content'],
|
|
301
|
+
external: {
|
|
302
|
+
fetch: {
|
|
303
|
+
backend: ['https://api.example.com']
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
309
|
+
const requiredPermissions = {
|
|
310
|
+
scopes: ['read:confluence-content', 'write:jira-work'],
|
|
311
|
+
external: {
|
|
312
|
+
fetch: {
|
|
313
|
+
backend: ['https://api.example.com', 'https://api.unauthorized.com'],
|
|
314
|
+
client: ['https://cdn.example.com']
|
|
315
|
+
},
|
|
316
|
+
images: ['https://images.example.com']
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
320
|
+
await (0, react_hooks_1.act)(async () => {
|
|
321
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
322
|
+
});
|
|
323
|
+
expect(result.current.isLoading).toBe(false);
|
|
324
|
+
expect(result.current.hasPermission).toBe(false);
|
|
325
|
+
expect(result.current.missingPermissions).toEqual({
|
|
326
|
+
scopes: ['write:jira-work'],
|
|
327
|
+
external: {
|
|
328
|
+
fetch: {
|
|
329
|
+
backend: ['https://api.unauthorized.com'],
|
|
330
|
+
client: ['https://cdn.example.com']
|
|
331
|
+
},
|
|
332
|
+
images: ['https://images.example.com']
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe('Edge cases', () => {
|
|
338
|
+
it('should handle empty required permissions', async () => {
|
|
339
|
+
const mockContext = {
|
|
340
|
+
permissions: {
|
|
341
|
+
scopes: ['read:confluence-content']
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
345
|
+
const requiredPermissions = {};
|
|
346
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
347
|
+
await (0, react_hooks_1.act)(async () => {
|
|
348
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
349
|
+
});
|
|
350
|
+
expect(result.current.isLoading).toBe(false);
|
|
351
|
+
expect(result.current.hasPermission).toBe(true);
|
|
352
|
+
expect(result.current.missingPermissions).toBe(null);
|
|
353
|
+
});
|
|
354
|
+
it('should handle complex external permission objects', async () => {
|
|
355
|
+
const mockContext = {
|
|
356
|
+
permissions: {
|
|
357
|
+
external: {
|
|
358
|
+
fetch: {
|
|
359
|
+
backend: [
|
|
360
|
+
'https://api.example.com',
|
|
361
|
+
{ address: 'https://api.address.com' },
|
|
362
|
+
{ remote: 'https://api.remote.com' }
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
mockGetContext.mockResolvedValue(mockContext);
|
|
369
|
+
const requiredPermissions = {
|
|
370
|
+
external: {
|
|
371
|
+
fetch: {
|
|
372
|
+
backend: ['https://api.example.com', 'https://api.address.com']
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const { result } = (0, react_hooks_1.renderHook)(() => (0, usePermissions_1.usePermissions)(requiredPermissions));
|
|
377
|
+
await (0, react_hooks_1.act)(async () => {
|
|
378
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
379
|
+
});
|
|
380
|
+
expect(result.current.isLoading).toBe(false);
|
|
381
|
+
expect(result.current.hasPermission).toBe(true);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: reuse logic in permissions.ts from @forge/api
|
|
3
|
+
*/
|
|
4
|
+
export interface PermissionRequirements {
|
|
5
|
+
scopes?: string[];
|
|
6
|
+
external?: {
|
|
7
|
+
fetch?: {
|
|
8
|
+
backend?: string[];
|
|
9
|
+
client?: string[];
|
|
10
|
+
};
|
|
11
|
+
fonts?: string[];
|
|
12
|
+
styles?: string[];
|
|
13
|
+
frames?: string[];
|
|
14
|
+
images?: string[];
|
|
15
|
+
media?: string[];
|
|
16
|
+
scripts?: string[];
|
|
17
|
+
};
|
|
18
|
+
content?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Missing permissions information
|
|
22
|
+
*/
|
|
23
|
+
export interface MissingPermissions {
|
|
24
|
+
scopes?: string[];
|
|
25
|
+
external?: {
|
|
26
|
+
fetch?: {
|
|
27
|
+
backend?: string[];
|
|
28
|
+
client?: string[];
|
|
29
|
+
};
|
|
30
|
+
fonts?: string[];
|
|
31
|
+
styles?: string[];
|
|
32
|
+
frames?: string[];
|
|
33
|
+
images?: string[];
|
|
34
|
+
media?: string[];
|
|
35
|
+
scripts?: string[];
|
|
36
|
+
};
|
|
37
|
+
content?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Permission check result
|
|
41
|
+
*/
|
|
42
|
+
export interface PermissionCheckResult {
|
|
43
|
+
granted: boolean;
|
|
44
|
+
missing: MissingPermissions | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Hook for checking permissions in Forge apps
|
|
48
|
+
*
|
|
49
|
+
* @param requiredPermissions - The permissions required for the component
|
|
50
|
+
* @returns Object containing permission state and loading status
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* const MyComponent: React.FC = () => {
|
|
55
|
+
* const { hasPermission, isLoading, missingPermissions } = usePermissions({
|
|
56
|
+
* scopes: ['write:confluence-content'],
|
|
57
|
+
* external: {
|
|
58
|
+
* fetch: {
|
|
59
|
+
* backend: ['https://api.example.com']
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* if (isLoading) return <LoadingSpinner />;
|
|
65
|
+
*
|
|
66
|
+
* if (!hasPermission) {
|
|
67
|
+
* return <PermissionDenied missingPermissions={missingPermissions} />;
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* return <ProtectedFeature />;
|
|
71
|
+
* };
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare const usePermissions: (requiredPermissions: PermissionRequirements) => {
|
|
75
|
+
hasPermission: boolean;
|
|
76
|
+
isLoading: boolean;
|
|
77
|
+
missingPermissions: MissingPermissions | null;
|
|
78
|
+
error: Error | null;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=usePermissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePermissions.d.ts","sourceRoot":"","sources":["../../src/hooks/usePermissions.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE;YACN,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;YACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;SACnB,CAAC;QACF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE;YACN,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;YACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;SACnB,CAAC;QACF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACpC;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,cAAc,wBAAyB,sBAAsB;;;;;CAqJzE,CAAC"}
|