@forge/react
11.9.2-next.0-experimental-919607a11.10.0-next.1
out/hooks/usePermissions.js~
out/hooks/usePermissions.jsModified+29−120
Index: package/out/hooks/usePermissions.js
===================================================================
--- package/out/hooks/usePermissions.js
+++ package/out/hooks/usePermissions.js
@@ -2,35 +2,9 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePermissions = void 0;
const react_1 = require("react");
const bridge_1 = require("@forge/bridge");
-const egress_1 = require("@forge/egress");
/**
- * https://ecosystem-platform.atlassian.net/browse/DEPLOY-1411
- * Uses @forge/egress for URL matching (same logic as @forge/api)
- */
-/**
- * Helper function to extract URL string from external URL permissions.
- * Matches the implementation in @forge/api for consistency.
- */
-function extractUrlString(url) {
- if (typeof url === 'string') {
- return url;
- }
- if ('address' in url && url.address) {
- return url.address;
- }
- return url.remote || '';
-}
-/**
- * Resource types that can be loaded externally
- */
-const RESOURCE_TYPES = ['fonts', 'styles', 'frames', 'images', 'media', 'scripts'];
-/**
- * Fetch types for external requests
- */
-const FETCH_TYPES = ['backend', 'client'];
-/**
* Hook for checking permissions in Forge apps
*
* @param requiredPermissions - The permissions required for the component
* @returns Object containing permission state, loading status, and error information
@@ -68,8 +42,12 @@
const usePermissions = (requiredPermissions) => {
const [context, setContext] = (0, react_1.useState)();
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
const [error, setError] = (0, react_1.useState)(null);
+ const [permissionResult, setPermissionResult] = (0, react_1.useState)({
+ granted: false,
+ missing: null
+ });
// Load context on mount
(0, react_1.useEffect)(() => {
const loadContext = async () => {
try {
@@ -86,108 +64,39 @@
}
};
void loadContext();
}, []);
- // Permission checking utilities
- const permissionUtils = (0, react_1.useMemo)(() => {
- if (!context?.permissions)
- return null;
- const { scopes, external = {} } = context.permissions;
- const scopeArray = Array.isArray(scopes) ? scopes : Object.keys(scopes || {});
- return {
- hasScope: (scope) => scopeArray.includes(scope),
- canFetchFrom: (type, url) => {
- const fetchUrls = external.fetch?.[type];
- if (!fetchUrls?.length)
- return false;
- // Extract URLs and create egress filter
- const allowList = fetchUrls.map(extractUrlString).filter((u) => u.length > 0);
- if (allowList.length === 0)
- return false;
- const egressFilter = new egress_1.EgressFilteringService(allowList);
- // Backend: hostname-only matching, Client: CSP validation (includes paths)
- return type === 'client' ? egressFilter.isValidUrlCSP(url) : egressFilter.isValidUrl(url);
- },
- canLoadResource: (type, url) => {
- const resourceUrls = external[type];
- if (!resourceUrls?.length)
- return false;
- // Extract URLs and create egress filter
- const allowList = resourceUrls.map(extractUrlString).filter((u) => u.length > 0);
- if (allowList.length === 0)
- return false;
- const egressFilter = new egress_1.EgressFilteringService(allowList);
- // All resources use CSP validation (checks protocol + hostname + paths)
- return egressFilter.isValidUrlCSP(url);
- },
- getScopes: () => scopeArray,
- getExternalPermissions: () => external,
- hasAnyPermissions: () => scopeArray.length > 0 || Object.keys(external).length > 0
- };
- }, [context?.permissions]);
- // Check permissions
- const permissionResult = (0, react_1.useMemo)(() => {
- if (!requiredPermissions) {
- return { granted: false, missing: null };
+ // Check permissions using shared utility
+ (0, react_1.useEffect)(() => {
+ // Skip if still loading context
+ if (isLoading) {
+ return;
}
- if (!permissionUtils) {
- // If still loading or there's an error, return null for missing permissions
- if (isLoading || error) {
- return { granted: false, missing: null };
+ const checkPerms = async () => {
+ if (!requiredPermissions) {
+ setPermissionResult({ granted: false, missing: null });
+ return;
}
- throw new Error('This feature is not available yet');
- }
- const missing = {};
- let hasAllRequiredPermissions = true;
- // Check scopes
- if (requiredPermissions.scopes?.length) {
- const missingScopes = requiredPermissions.scopes.filter((scope) => !permissionUtils.hasScope(scope));
- if (missingScopes.length > 0) {
- missing.scopes = missingScopes;
- hasAllRequiredPermissions = false;
- }
- }
- // Check external permissions
- if (requiredPermissions.external) {
- const missingExternal = {};
- // Check fetch permissions
- if (requiredPermissions.external.fetch) {
- const missingFetch = {};
- FETCH_TYPES.forEach((type) => {
- const requiredUrls = requiredPermissions.external?.fetch?.[type];
- if (requiredUrls?.length) {
- const missingUrls = requiredUrls.filter((url) => !permissionUtils.canFetchFrom(type, url));
- if (missingUrls.length > 0) {
- missingFetch[type] = missingUrls;
- hasAllRequiredPermissions = false;
- }
- }
- });
- if (Object.keys(missingFetch).length > 0) {
- missingExternal.fetch = missingFetch;
+ if (!context?.permissions) {
+ // If context loaded but has no permissions, set error
+ if (context !== undefined) {
+ setError(new Error('This feature is not available yet'));
+ setPermissionResult({ granted: false, missing: null });
}
+ return;
}
- // Check resource permissions
- RESOURCE_TYPES.forEach((type) => {
- const requiredUrls = requiredPermissions.external?.[type];
- if (requiredUrls?.length) {
- const missingUrls = requiredUrls.filter((url) => !permissionUtils.canLoadResource(type, url));
- if (missingUrls.length > 0) {
- missingExternal[type] = missingUrls;
- hasAllRequiredPermissions = false;
- }
- }
- });
- if (Object.keys(missingExternal).length > 0) {
- missing.external = missingExternal;
+ try {
+ setError(null); // Clear any previous errors
+ const result = await (0, bridge_1.checkPermissions)(requiredPermissions, context.permissions);
+ setPermissionResult(result);
}
- }
- // Note: Content permissions are not supported in the current RuntimePermissions type
- return {
- granted: hasAllRequiredPermissions,
- missing: hasAllRequiredPermissions ? null : missing
+ catch (err) {
+ setError(err instanceof Error ? err : new Error('Failed to check permissions'));
+ setPermissionResult({ granted: false, missing: null });
+ }
};
- }, [permissionUtils, requiredPermissions]);
+ void checkPerms();
+ }, [context, requiredPermissions, isLoading]);
return {
hasPermission: permissionResult.granted,
isLoading,
missingPermissions: permissionResult.missing,