@forge/bridge

5.15.2-next.0-experimental-5b726e65.16.0-next.1
out/permissions/permissionsUtil.js
~out/permissions/permissionsUtil.jsModified
+52
Index: package/out/permissions/permissionsUtil.js
===================================================================
--- package/out/permissions/permissionsUtil.js
+++ package/out/permissions/permissionsUtil.js
@@ -2,8 +2,12 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.checkPermissions = exports.createPermissionUtils = void 0;
 const egress_1 = require("@forge/egress");
 const view_1 = require("../view");
+/**
+ * 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;
     }
@@ -11,10 +15,21 @@
         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'];
+/**
+ * Create permission utilities from runtime permissions
+ * @param runtimePermissions - The runtime permissions from context
+ * @returns Permission utilities or null if permissions are not available
+ */
 function createPermissionUtils(runtimePermissions) {
     if (!runtimePermissions) {
         return null;
     }
@@ -26,23 +41,29 @@
             var _a;
             const fetchUrls = (_a = external.fetch) === null || _a === void 0 ? void 0 : _a[type];
             if (!(fetchUrls === null || fetchUrls === void 0 ? void 0 : 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)
+            // Type assertion needed because node_modules may have outdated types
             const egressFilterWithCSP = egressFilter;
             return type === 'client' ? egressFilterWithCSP.isValidUrlCSP(url) : egressFilter.isValidUrl(url);
         },
         canLoadResource: (type, url) => {
             const resourceUrls = external[type];
             if (!(resourceUrls === null || resourceUrls === void 0 ? void 0 : 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)
+            // Type assertion needed because node_modules may have outdated types
             const egressFilterWithCSP = egressFilter;
             return egressFilterWithCSP.isValidUrlCSP(url);
         },
         getScopes: () => scopeArray,
@@ -50,15 +71,21 @@
         hasAnyPermissions: () => scopeArray.length > 0 || Object.keys(external).length > 0
     };
 }
 exports.createPermissionUtils = createPermissionUtils;
+/**
+ * Check if required scopes are granted
+ */
 function checkScopes(requiredScopes, permissionUtils) {
     if (!(requiredScopes === null || requiredScopes === void 0 ? void 0 : requiredScopes.length)) {
         return undefined;
     }
     const missingScopes = requiredScopes.filter((scope) => !permissionUtils.hasScope(scope));
     return missingScopes.length > 0 ? missingScopes : undefined;
 }
+/**
+ * Check if required fetch permissions are granted
+ */
 function checkFetchPermissions(requiredFetch, permissionUtils) {
     if (!(requiredFetch === null || requiredFetch === void 0 ? void 0 : requiredFetch.fetch)) {
         return undefined;
     }
@@ -74,8 +101,11 @@
         }
     });
     return Object.keys(missingFetch).length > 0 ? missingFetch : undefined;
 }
+/**
+ * Check if required resource permissions are granted
+ */
 function checkResourcePermissions(requiredExternal, permissionUtils) {
     const missingResources = {};
     RESOURCE_TYPES.forEach((type) => {
         const requiredUrls = requiredExternal === null || requiredExternal === void 0 ? void 0 : requiredExternal[type];
@@ -87,8 +117,11 @@
         }
     });
     return Object.keys(missingResources).length > 0 ? missingResources : undefined;
 }
+/**
+ * Check if required external permissions are granted
+ */
 function checkExternalPermissions(requiredExternal, permissionUtils) {
     if (!requiredExternal) {
         return undefined;
     }
@@ -105,20 +138,26 @@
         Object.assign(missingExternal, missingResources);
     }
     return missingExternal;
 }
+// Throws if a field that should be a plain object but is something else.
+// Only used for external and external.fetch, which are guarded above.
 function validateObjectField(value, fieldPath) {
     if (value !== undefined) {
         if (typeof value !== 'object' || value === null || Array.isArray(value)) {
             throw new TypeError(`${fieldPath} should be an object, not ${Array.isArray(value) ? 'an array' : `a ${typeof value}`}`);
         }
     }
 }
+// Throws if a field that should be an array but is something else.
 function validateArrayField(value, fieldPath) {
     if (value !== undefined && !Array.isArray(value)) {
         throw new TypeError(`${fieldPath} should be an array, not a ${typeof value}`);
     }
 }
+/**
+ * Validates that fields in requiredPermissions have the correct types.
+ */
 function validatePermissionShape(requiredPermissions) {
     validateArrayField(requiredPermissions.scopes, 'scopes');
     const external = requiredPermissions.external;
     if (external === undefined)
@@ -133,38 +172,51 @@
     for (const type of RESOURCE_TYPES) {
         validateArrayField(external[type], `external.${type}`);
     }
 }
+/**
+ * Check if required permissions are granted
+ * @param requiredPermissions - The permissions required to check
+ * @param runtimePermissions - The runtime permissions from context (optional, will be fetched from view.getContext() if not provided)
+ * @returns Promise resolving to permission check result with granted status and missing permissions
+ */
 async function checkPermissions(requiredPermissions, runtimePermissions) {
     var _a;
+    // Early return for falsy required permissions
     if (!requiredPermissions) {
         return { granted: false, missing: null };
     }
     validatePermissionShape(requiredPermissions);
+    // Early return for empty permissions (no requirements = all granted)
     if (!((_a = requiredPermissions.scopes) === null || _a === void 0 ? void 0 : _a.length) && !requiredPermissions.external) {
         return { granted: true, missing: null };
     }
+    // If runtimePermissions is not provided, fetch it from context
     let permissionsToCheck = runtimePermissions;
     if (!permissionsToCheck) {
         const context = await view_1.view.getContext();
         permissionsToCheck = context.permissions;
     }
     const permissionUtils = createPermissionUtils(permissionsToCheck);
     if (!permissionUtils) {
+        // If permissions are not available, return not granted
         return { granted: false, missing: null };
     }
     const missing = {};
     let hasAllRequiredPermissions = true;
+    // Check scopes
     const missingScopes = checkScopes(requiredPermissions.scopes, permissionUtils);
     if (missingScopes) {
         missing.scopes = missingScopes;
         hasAllRequiredPermissions = false;
     }
+    // Check external permissions
     const missingExternal = checkExternalPermissions(requiredPermissions.external, permissionUtils);
     if (missingExternal) {
         missing.external = missingExternal;
         hasAllRequiredPermissions = false;
     }
+    // Note: Content permissions are not supported in the current RuntimePermissions type
     return {
         granted: hasAllRequiredPermissions,
         missing: hasAllRequiredPermissions ? null : missing
     };