@forge/react

11.9.011.9.1-next.0
out/hooks/usePermissions.js
out/hooks/usePermissions.js
+29−30
Index: package/out/hooks/usePermissions.js
===================================================================
--- package/out/hooks/usePermissions.js
+++ package/out/hooks/usePermissions.js
@@ -2,32 +2,35 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.usePermissions = void 0;
 const react_1 = require("react");
 const bridge_1 = require("@forge/bridge");
-const minimatch_1 = require("minimatch");
+const egress_1 = require("@forge/egress");
 /**
  * https://ecosystem-platform.atlassian.net/browse/DEPLOY-1411
- * reuse logic from @forge/api
+ * 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'];
 /**
- * Helper function to check if a URL matches any of the allowed patterns
- * Uses minimatch for robust pattern matching with wildcards
- */
-const matchesAllowedUrl = (url, allowedUrls) => {
-    return allowedUrls.some((allowedUrl) => {
-        // Use minimatch for pattern matching
-        return (0, minimatch_1.minimatch)(url, allowedUrl);
-    });
-};
-/**
  * 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
@@ -95,31 +98,27 @@
             canFetchFrom: (type, url) => {
                 const fetchUrls = external.fetch?.[type];
                 if (!fetchUrls?.length)
                     return false;
-                // Extract string URLs from fetch URLs array
-                const allowedUrls = fetchUrls
-                    .map((item) => {
-                    // If item is already a string, use it directly
-                    if (typeof item === 'string') {
-                        return item;
-                    }
-                    // If item has an address property, use that
-                    if ('address' in item && item.address) {
-                        return item.address;
-                    }
-                    // Otherwise, use the remote property (if it exists)
-                    return item.remote;
-                })
-                    .filter((url) => typeof url === 'string');
-                return matchesAllowedUrl(url, allowedUrls);
+                // 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;
-                const stringUrls = resourceUrls.filter((item) => typeof item === 'string');
-                return matchesAllowedUrl(url, stringUrls);
+                // 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