@forge/react 11.9.0-next.1 → 11.9.1-experimental-60ea29e
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 +42 -0
- package/LICENSE.txt +1 -1
- package/out/hooks/__test__/usePermissions.test.js +381 -4
- package/out/hooks/usePermissions.d.ts +4 -31
- package/out/hooks/usePermissions.d.ts.map +1 -1
- package/out/hooks/usePermissions.js +29 -121
- package/package.json +3 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -3,29 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.usePermissions = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const bridge_1 = require("@forge/bridge");
|
|
6
|
-
const minimatch_1 = require("minimatch");
|
|
7
|
-
/**
|
|
8
|
-
* https://ecosystem-platform.atlassian.net/browse/DEPLOY-1411
|
|
9
|
-
* reuse logic from @forge/api
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Resource types that can be loaded externally
|
|
13
|
-
*/
|
|
14
|
-
const RESOURCE_TYPES = ['fonts', 'styles', 'frames', 'images', 'media', 'scripts'];
|
|
15
|
-
/**
|
|
16
|
-
* Fetch types for external requests
|
|
17
|
-
*/
|
|
18
|
-
const FETCH_TYPES = ['backend', 'client'];
|
|
19
|
-
/**
|
|
20
|
-
* Helper function to check if a URL matches any of the allowed patterns
|
|
21
|
-
* Uses minimatch for robust pattern matching with wildcards
|
|
22
|
-
*/
|
|
23
|
-
const matchesAllowedUrl = (url, allowedUrls) => {
|
|
24
|
-
return allowedUrls.some((allowedUrl) => {
|
|
25
|
-
// Use minimatch for pattern matching
|
|
26
|
-
return (0, minimatch_1.minimatch)(url, allowedUrl);
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
6
|
/**
|
|
30
7
|
* Hook for checking permissions in Forge apps
|
|
31
8
|
*
|
|
@@ -66,6 +43,10 @@ const usePermissions = (requiredPermissions) => {
|
|
|
66
43
|
const [context, setContext] = (0, react_1.useState)();
|
|
67
44
|
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
68
45
|
const [error, setError] = (0, react_1.useState)(null);
|
|
46
|
+
const [permissionResult, setPermissionResult] = (0, react_1.useState)({
|
|
47
|
+
granted: false,
|
|
48
|
+
missing: null
|
|
49
|
+
});
|
|
69
50
|
// Load context on mount
|
|
70
51
|
(0, react_1.useEffect)(() => {
|
|
71
52
|
const loadContext = async () => {
|
|
@@ -84,110 +65,37 @@ const usePermissions = (requiredPermissions) => {
|
|
|
84
65
|
};
|
|
85
66
|
void loadContext();
|
|
86
67
|
}, []);
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
if
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const scopeArray = Array.isArray(scopes) ? scopes : Object.keys(scopes || {});
|
|
93
|
-
return {
|
|
94
|
-
hasScope: (scope) => scopeArray.includes(scope),
|
|
95
|
-
canFetchFrom: (type, url) => {
|
|
96
|
-
const fetchUrls = external.fetch?.[type];
|
|
97
|
-
if (!fetchUrls?.length)
|
|
98
|
-
return false;
|
|
99
|
-
// Extract string URLs from fetch URLs array
|
|
100
|
-
const allowedUrls = fetchUrls
|
|
101
|
-
.map((item) => {
|
|
102
|
-
// If item is already a string, use it directly
|
|
103
|
-
if (typeof item === 'string') {
|
|
104
|
-
return item;
|
|
105
|
-
}
|
|
106
|
-
// If item has an address property, use that
|
|
107
|
-
if ('address' in item && item.address) {
|
|
108
|
-
return item.address;
|
|
109
|
-
}
|
|
110
|
-
// Otherwise, use the remote property (if it exists)
|
|
111
|
-
return item.remote;
|
|
112
|
-
})
|
|
113
|
-
.filter((url) => typeof url === 'string');
|
|
114
|
-
return matchesAllowedUrl(url, allowedUrls);
|
|
115
|
-
},
|
|
116
|
-
canLoadResource: (type, url) => {
|
|
117
|
-
const resourceUrls = external[type];
|
|
118
|
-
if (!resourceUrls?.length)
|
|
119
|
-
return false;
|
|
120
|
-
const stringUrls = resourceUrls.filter((item) => typeof item === 'string');
|
|
121
|
-
return matchesAllowedUrl(url, stringUrls);
|
|
122
|
-
},
|
|
123
|
-
getScopes: () => scopeArray,
|
|
124
|
-
getExternalPermissions: () => external,
|
|
125
|
-
hasAnyPermissions: () => scopeArray.length > 0 || Object.keys(external).length > 0
|
|
126
|
-
};
|
|
127
|
-
}, [context?.permissions]);
|
|
128
|
-
// Check permissions
|
|
129
|
-
const permissionResult = (0, react_1.useMemo)(() => {
|
|
130
|
-
if (!requiredPermissions) {
|
|
131
|
-
return { granted: false, missing: null };
|
|
132
|
-
}
|
|
133
|
-
if (!permissionUtils) {
|
|
134
|
-
// If still loading or there's an error, return null for missing permissions
|
|
135
|
-
if (isLoading || error) {
|
|
136
|
-
return { granted: false, missing: null };
|
|
137
|
-
}
|
|
138
|
-
throw new Error('This feature is not available yet');
|
|
68
|
+
// Check permissions using shared utility
|
|
69
|
+
(0, react_1.useEffect)(() => {
|
|
70
|
+
// Skip if still loading context
|
|
71
|
+
if (isLoading) {
|
|
72
|
+
return;
|
|
139
73
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const missingScopes = requiredPermissions.scopes.filter((scope) => !permissionUtils.hasScope(scope));
|
|
145
|
-
if (missingScopes.length > 0) {
|
|
146
|
-
missing.scopes = missingScopes;
|
|
147
|
-
hasAllRequiredPermissions = false;
|
|
74
|
+
const checkPerms = async () => {
|
|
75
|
+
if (!requiredPermissions) {
|
|
76
|
+
setPermissionResult({ granted: false, missing: null });
|
|
77
|
+
return;
|
|
148
78
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (requiredPermissions.external.fetch) {
|
|
155
|
-
const missingFetch = {};
|
|
156
|
-
FETCH_TYPES.forEach((type) => {
|
|
157
|
-
const requiredUrls = requiredPermissions.external?.fetch?.[type];
|
|
158
|
-
if (requiredUrls?.length) {
|
|
159
|
-
const missingUrls = requiredUrls.filter((url) => !permissionUtils.canFetchFrom(type, url));
|
|
160
|
-
if (missingUrls.length > 0) {
|
|
161
|
-
missingFetch[type] = missingUrls;
|
|
162
|
-
hasAllRequiredPermissions = false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
if (Object.keys(missingFetch).length > 0) {
|
|
167
|
-
missingExternal.fetch = missingFetch;
|
|
79
|
+
if (!context?.permissions) {
|
|
80
|
+
// If context loaded but has no permissions, set error
|
|
81
|
+
if (context !== undefined) {
|
|
82
|
+
setError(new Error('This feature is not available yet'));
|
|
83
|
+
setPermissionResult({ granted: false, missing: null });
|
|
168
84
|
}
|
|
85
|
+
return;
|
|
169
86
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
if (Object.keys(missingExternal).length > 0) {
|
|
182
|
-
missing.external = missingExternal;
|
|
87
|
+
try {
|
|
88
|
+
setError(null); // Clear any previous errors
|
|
89
|
+
const result = await (0, bridge_1.checkPermissions)(requiredPermissions, context.permissions);
|
|
90
|
+
setPermissionResult(result);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
setError(err instanceof Error ? err : new Error('Failed to check permissions'));
|
|
94
|
+
setPermissionResult({ granted: false, missing: null });
|
|
183
95
|
}
|
|
184
|
-
}
|
|
185
|
-
// Note: Content permissions are not supported in the current RuntimePermissions type
|
|
186
|
-
return {
|
|
187
|
-
granted: hasAllRequiredPermissions,
|
|
188
|
-
missing: hasAllRequiredPermissions ? null : missing
|
|
189
96
|
};
|
|
190
|
-
|
|
97
|
+
void checkPerms();
|
|
98
|
+
}, [context, requiredPermissions, isLoading]);
|
|
191
99
|
return {
|
|
192
100
|
hasPermission: permissionResult.granted,
|
|
193
101
|
isLoading,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/react",
|
|
3
|
-
"version": "11.9.
|
|
3
|
+
"version": "11.9.1-experimental-60ea29e",
|
|
4
4
|
"description": "Forge React reconciler",
|
|
5
5
|
"author": "Atlassian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"@atlaskit/adf-schema": "^48.0.0",
|
|
29
29
|
"@atlaskit/adf-utils": "^19.19.0",
|
|
30
30
|
"@atlaskit/forge-react-types": "^0.48.0",
|
|
31
|
-
"@forge/bridge": "^5.10.
|
|
31
|
+
"@forge/bridge": "^5.10.2-experimental-60ea29e",
|
|
32
|
+
"@forge/egress": "^2.3.1",
|
|
32
33
|
"@forge/i18n": "0.0.7",
|
|
33
34
|
"@types/react": "^18.2.64",
|
|
34
35
|
"@types/react-reconciler": "^0.28.8",
|
|
35
36
|
"lodash": "^4.17.21",
|
|
36
|
-
"minimatch": "^9.0.5",
|
|
37
37
|
"react": "^18.2.0",
|
|
38
38
|
"react-hook-form": "7.65.0",
|
|
39
39
|
"react-reconciler": "^0.29.0",
|