@backstage/frontend-app-api 0.16.0-next.1 → 0.16.1

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/frontend-internal/src/wiring/InternalExtensionDefinition.esm.js.map +1 -1
  3. package/dist/frontend-internal/src/wiring/InternalFrontendPlugin.esm.js.map +1 -1
  4. package/dist/frontend-plugin-api/src/wiring/createFrontendModule.esm.js.map +1 -1
  5. package/dist/frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js.map +1 -1
  6. package/dist/index.d.ts +145 -26
  7. package/dist/index.esm.js +1 -0
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/routing/RouteAliasResolver.esm.js +0 -2
  10. package/dist/routing/RouteAliasResolver.esm.js.map +1 -1
  11. package/dist/routing/RouteResolver.esm.js +1 -1
  12. package/dist/routing/resolveRouteBindings.esm.js +2 -0
  13. package/dist/routing/resolveRouteBindings.esm.js.map +1 -1
  14. package/dist/tree/instantiateAppNodeTree.esm.js +72 -15
  15. package/dist/tree/instantiateAppNodeTree.esm.js.map +1 -1
  16. package/dist/tree/resolveAppNodeSpecs.esm.js +50 -9
  17. package/dist/tree/resolveAppNodeSpecs.esm.js.map +1 -1
  18. package/dist/wiring/FrontendApiRegistry.esm.js +92 -0
  19. package/dist/wiring/FrontendApiRegistry.esm.js.map +1 -0
  20. package/dist/wiring/apiFactories.esm.js +185 -0
  21. package/dist/wiring/apiFactories.esm.js.map +1 -0
  22. package/dist/wiring/createErrorCollector.esm.js.map +1 -1
  23. package/dist/wiring/createPluginInfoAttacher.esm.js.map +1 -1
  24. package/dist/wiring/createSpecializedApp.esm.js +11 -285
  25. package/dist/wiring/createSpecializedApp.esm.js.map +1 -1
  26. package/dist/wiring/phaseApis.esm.js +161 -0
  27. package/dist/wiring/phaseApis.esm.js.map +1 -0
  28. package/dist/wiring/predicates.esm.js +116 -0
  29. package/dist/wiring/predicates.esm.js.map +1 -0
  30. package/dist/wiring/prepareSpecializedApp.esm.js +518 -0
  31. package/dist/wiring/prepareSpecializedApp.esm.js.map +1 -0
  32. package/dist/wiring/treeLifecycle.esm.js +186 -0
  33. package/dist/wiring/treeLifecycle.esm.js.map +1 -0
  34. package/package.json +16 -14
  35. package/dist/core-app-api/src/apis/system/ApiRegistry.esm.js +0 -50
  36. package/dist/core-app-api/src/apis/system/ApiRegistry.esm.js.map +0 -1
@@ -0,0 +1,161 @@
1
+ import { createApiFactory, appTreeApiRef, configApiRef, routeResolutionApiRef, identityApiRef } from '@backstage/frontend-plugin-api';
2
+ import { matchRoutes } from 'react-router-dom';
3
+ import { AppIdentityProxy } from '../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.esm.js';
4
+ import { createRouteAliasResolver } from '../routing/RouteAliasResolver.esm.js';
5
+ import { RouteResolver } from '../routing/RouteResolver.esm.js';
6
+ import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode.esm.js';
7
+ import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree.esm.js';
8
+ import { FrontendApiRegistry, FrontendApiResolver } from './FrontendApiRegistry.esm.js';
9
+
10
+ class AppTreeApiProxy {
11
+ #routeInfo;
12
+ tree;
13
+ appBasePath;
14
+ constructor(tree, appBasePath) {
15
+ this.tree = tree;
16
+ this.appBasePath = appBasePath;
17
+ }
18
+ checkIfInitialized() {
19
+ if (!this.#routeInfo) {
20
+ throw new Error(
21
+ `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`
22
+ );
23
+ }
24
+ }
25
+ getTree() {
26
+ this.checkIfInitialized();
27
+ return { tree: this.tree };
28
+ }
29
+ getNodesByRoutePath(routePath) {
30
+ this.checkIfInitialized();
31
+ const routeInfo = this.#routeInfo;
32
+ if (!routeInfo) {
33
+ throw new Error(
34
+ `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`
35
+ );
36
+ }
37
+ let path = routePath;
38
+ if (path.startsWith(this.appBasePath)) {
39
+ path = path.slice(this.appBasePath.length);
40
+ }
41
+ const matchedRoutes = matchRoutes(routeInfo.routeObjects, path);
42
+ const matchedAppNodes = matchedRoutes?.flatMap((routeObj) => {
43
+ const appNode = routeObj.route.appNode;
44
+ return appNode ? [appNode] : [];
45
+ }) || [];
46
+ return { nodes: matchedAppNodes };
47
+ }
48
+ initialize(routeInfo) {
49
+ this.#routeInfo = routeInfo;
50
+ }
51
+ }
52
+ class RouteResolutionApiProxy {
53
+ #delegate;
54
+ #routeObjects;
55
+ routeBindings;
56
+ appBasePath;
57
+ constructor(routeBindings, appBasePath) {
58
+ this.routeBindings = routeBindings;
59
+ this.appBasePath = appBasePath;
60
+ }
61
+ resolve(anyRouteRef, options) {
62
+ if (!this.#delegate) {
63
+ throw new Error(
64
+ `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`
65
+ );
66
+ }
67
+ return this.#delegate.resolve(anyRouteRef, options);
68
+ }
69
+ initialize(routeInfo, routeRefsById) {
70
+ this.#delegate = new RouteResolver(
71
+ routeInfo.routePaths,
72
+ routeInfo.routeParents,
73
+ routeInfo.routeObjects,
74
+ this.routeBindings,
75
+ this.appBasePath,
76
+ routeInfo.routeAliasResolver,
77
+ routeRefsById
78
+ );
79
+ this.#routeObjects = routeInfo.routeObjects;
80
+ return routeInfo;
81
+ }
82
+ getRouteObjects() {
83
+ return this.#routeObjects;
84
+ }
85
+ }
86
+ class PreparedAppIdentityProxy extends AppIdentityProxy {
87
+ #onTargetSet;
88
+ setTargetHandlers(options) {
89
+ this.#onTargetSet = options.onTargetSet;
90
+ }
91
+ clearTargetHandlers() {
92
+ this.#onTargetSet = void 0;
93
+ }
94
+ setTarget(identityApi, targetOptions) {
95
+ super.setTarget(identityApi, targetOptions);
96
+ const onTargetSet = this.#onTargetSet;
97
+ if (!onTargetSet) {
98
+ return;
99
+ }
100
+ this.clearTargetHandlers();
101
+ onTargetSet(identityApi);
102
+ }
103
+ }
104
+ function createPhaseApis(options) {
105
+ const appTreeApi = new AppTreeApiProxy(options.tree, options.appBasePath);
106
+ const routeResolutionApi = new RouteResolutionApiProxy(
107
+ options.routeBindings,
108
+ options.appBasePath
109
+ );
110
+ const identityProxy = new PreparedAppIdentityProxy();
111
+ const phaseApiRegistry = new FrontendApiRegistry();
112
+ phaseApiRegistry.registerAll([
113
+ createApiFactory(appTreeApiRef, appTreeApi),
114
+ ...options.includeConfigApi ? [createApiFactory(configApiRef, options.config)] : [],
115
+ createApiFactory(routeResolutionApiRef, routeResolutionApi),
116
+ createApiFactory(identityApiRef, identityProxy),
117
+ ...options.staticFactories
118
+ ]);
119
+ const apis = new FrontendApiResolver({
120
+ primaryRegistry: phaseApiRegistry,
121
+ secondaryRegistry: options.appApiRegistry,
122
+ fallbackApis: options.fallbackApis
123
+ });
124
+ return {
125
+ apis,
126
+ routeResolutionApi,
127
+ appTreeApi,
128
+ identityApiProxy: identityProxy
129
+ };
130
+ }
131
+ function instantiateAndInitializePhaseTree(options) {
132
+ instantiateAppNodeTree(
133
+ options.tree.root,
134
+ options.apis,
135
+ options.collector,
136
+ options.extensionFactoryMiddleware,
137
+ {
138
+ ...options.stopAtAttachment ? { stopAtAttachment: options.stopAtAttachment } : {},
139
+ skipChild: options.skipChild,
140
+ onMissingApi: options.onMissingApi,
141
+ predicateContext: options.predicateContext
142
+ }
143
+ );
144
+ const routeInfo = extractRouteInfoFromAppNode(
145
+ options.tree.root,
146
+ createRouteAliasResolver(options.routeRefsById)
147
+ );
148
+ options.routeResolutionApi.initialize(
149
+ routeInfo,
150
+ options.routeRefsById.routes
151
+ );
152
+ options.appTreeApi.initialize(routeInfo);
153
+ }
154
+ function setIdentityApiTarget(options) {
155
+ options.identityApiProxy.setTarget(options.identityApi, {
156
+ signOutTargetUrl: options.signOutTargetUrl
157
+ });
158
+ }
159
+
160
+ export { AppTreeApiProxy, PreparedAppIdentityProxy, RouteResolutionApiProxy, createPhaseApis, instantiateAndInitializePhaseTree, setIdentityApiTarget };
161
+ //# sourceMappingURL=phaseApis.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phaseApis.esm.js","sources":["../../src/wiring/phaseApis.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AnyApiFactory,\n ApiHolder,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n ConfigApi,\n configApiRef,\n createApiFactory,\n ExternalRouteRef,\n identityApiRef,\n RouteFunc,\n RouteRef,\n RouteResolutionApi,\n routeResolutionApiRef,\n SubRouteRef,\n type AnyRouteRefParams,\n type AppNode,\n type ExtensionFactoryMiddleware,\n type IdentityApi,\n} from '@backstage/frontend-plugin-api';\nimport { matchRoutes } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { createRouteAliasResolver } from '../routing/RouteAliasResolver';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { collectRouteIds } from '../routing/collectRouteIds';\nimport {\n extractRouteInfoFromAppNode,\n type RouteInfo,\n} from '../routing/extractRouteInfoFromAppNode';\nimport { type BackstageRouteObject } from '../routing/types';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\nimport {\n FrontendApiRegistry,\n FrontendApiResolver,\n} from './FrontendApiRegistry';\nimport { type ExtensionPredicateContext } from './predicates';\nimport { type ErrorCollector } from './createErrorCollector';\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nexport class AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n private readonly tree: AppTree;\n private readonly appBasePath: string;\n\n constructor(tree: AppTree, appBasePath: string) {\n this.tree = tree;\n this.appBasePath = appBasePath;\n }\n\n private checkIfInitialized() {\n if (!this.#routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n }\n\n getTree() {\n this.checkIfInitialized();\n\n return { tree: this.tree };\n }\n\n getNodesByRoutePath(routePath: string): { nodes: AppNode[] } {\n this.checkIfInitialized();\n const routeInfo = this.#routeInfo;\n if (!routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n let path = routePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(routeInfo.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes?.flatMap(routeObj => {\n const appNode = routeObj.route.appNode;\n return appNode ? [appNode] : [];\n }) || [];\n\n return { nodes: matchedAppNodes };\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#routeInfo = routeInfo;\n }\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nexport class RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n private readonly routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;\n private readonly appBasePath: string;\n\n constructor(\n routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>,\n appBasePath: string,\n ) {\n this.routeBindings = routeBindings;\n this.appBasePath = appBasePath;\n }\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: { sourcePath?: string },\n ): RouteFunc<TParams> | undefined {\n if (!this.#delegate) {\n throw new Error(\n `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n return this.#delegate.resolve(anyRouteRef, options);\n }\n\n initialize(\n routeInfo: RouteInfo,\n routeRefsById: Map<string, RouteRef | SubRouteRef>,\n ) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n routeInfo.routeAliasResolver,\n routeRefsById,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\n }\n}\n\nexport class PreparedAppIdentityProxy extends AppIdentityProxy {\n #onTargetSet?:\n | ((identityApi: Parameters<AppIdentityProxy['setTarget']>[0]) => void)\n | undefined;\n\n setTargetHandlers(options: {\n onTargetSet?(\n identityApi: Parameters<AppIdentityProxy['setTarget']>[0],\n ): void;\n }) {\n this.#onTargetSet = options.onTargetSet;\n }\n\n clearTargetHandlers() {\n this.#onTargetSet = undefined;\n }\n\n override setTarget(\n identityApi: Parameters<AppIdentityProxy['setTarget']>[0],\n targetOptions: Parameters<AppIdentityProxy['setTarget']>[1],\n ) {\n super.setTarget(identityApi, targetOptions);\n\n const onTargetSet = this.#onTargetSet;\n if (!onTargetSet) {\n return;\n }\n\n this.clearTargetHandlers();\n onTargetSet(identityApi);\n }\n}\n\nexport function createPhaseApis(options: {\n tree: AppTree;\n config: ConfigApi;\n appApiRegistry: FrontendApiRegistry;\n fallbackApis?: ApiHolder;\n includeConfigApi: boolean;\n appBasePath: string;\n routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;\n staticFactories: AnyApiFactory[];\n}) {\n const appTreeApi = new AppTreeApiProxy(options.tree, options.appBasePath);\n const routeResolutionApi = new RouteResolutionApiProxy(\n options.routeBindings,\n options.appBasePath,\n );\n const identityProxy = new PreparedAppIdentityProxy();\n const phaseApiRegistry = new FrontendApiRegistry();\n phaseApiRegistry.registerAll([\n createApiFactory(appTreeApiRef, appTreeApi),\n ...(options.includeConfigApi\n ? [createApiFactory(configApiRef, options.config)]\n : []),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, identityProxy),\n ...options.staticFactories,\n ]);\n\n const apis = new FrontendApiResolver({\n primaryRegistry: phaseApiRegistry,\n secondaryRegistry: options.appApiRegistry,\n fallbackApis: options.fallbackApis,\n });\n\n return {\n apis,\n routeResolutionApi,\n appTreeApi,\n identityApiProxy: identityProxy,\n };\n}\n\nexport function instantiateAndInitializePhaseTree(options: {\n tree: AppTree;\n apis: ApiHolder;\n collector: ErrorCollector;\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware;\n routeResolutionApi: RouteResolutionApiProxy;\n appTreeApi: AppTreeApiProxy;\n routeRefsById: ReturnType<typeof collectRouteIds>;\n skipChild?(ctx: { node: AppNode; input: string; child: AppNode }): boolean;\n onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void;\n predicateContext?: ExtensionPredicateContext;\n stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean;\n}) {\n instantiateAppNodeTree(\n options.tree.root,\n options.apis,\n options.collector,\n options.extensionFactoryMiddleware,\n {\n ...(options.stopAtAttachment\n ? { stopAtAttachment: options.stopAtAttachment }\n : {}),\n skipChild: options.skipChild,\n onMissingApi: options.onMissingApi,\n predicateContext: options.predicateContext,\n },\n );\n\n const routeInfo = extractRouteInfoFromAppNode(\n options.tree.root,\n createRouteAliasResolver(options.routeRefsById),\n );\n\n options.routeResolutionApi.initialize(\n routeInfo,\n options.routeRefsById.routes,\n );\n options.appTreeApi.initialize(routeInfo);\n}\n\nexport function setIdentityApiTarget(options: {\n identityApiProxy: AppIdentityProxy;\n identityApi: IdentityApi;\n signOutTargetUrl: string;\n}) {\n options.identityApiProxy.setTarget(options.identityApi, {\n signOutTargetUrl: options.signOutTargetUrl,\n });\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import { createApiRef, featureFlagsApiRef } from '@backstage/frontend-plugin-api';
2
+
3
+ const EMPTY_PREDICATE_CONTEXT = {
4
+ featureFlags: [],
5
+ permissions: []
6
+ };
7
+ const localPermissionApiRef = createApiRef({
8
+ id: "plugin.permission.api"
9
+ });
10
+ function createPredicateContextLoader(options) {
11
+ function getActiveFeatureFlags() {
12
+ const featureFlagsApi = options.apis.get(featureFlagsApiRef);
13
+ if (!featureFlagsApi) {
14
+ return [];
15
+ }
16
+ return options.predicateReferences.featureFlags.filter(
17
+ (name) => featureFlagsApi.isActive(name)
18
+ );
19
+ }
20
+ function getImmediate() {
21
+ if (options.predicateReferences.permissions.length > 0) {
22
+ const permissionApi = options.apis.get(localPermissionApiRef);
23
+ if (permissionApi) {
24
+ return void 0;
25
+ }
26
+ }
27
+ return {
28
+ featureFlags: getActiveFeatureFlags(),
29
+ permissions: []
30
+ };
31
+ }
32
+ async function load() {
33
+ const immediatePredicateContext = getImmediate();
34
+ if (immediatePredicateContext) {
35
+ return immediatePredicateContext;
36
+ }
37
+ let allowedPermissions = [];
38
+ const permissionApi = options.apis.get(localPermissionApiRef);
39
+ if (permissionApi) {
40
+ const permissionNames = options.predicateReferences.permissions;
41
+ const responses = await Promise.all(
42
+ permissionNames.map(
43
+ (name) => permissionApi.authorize({
44
+ permission: { name, type: "basic", attributes: {} }
45
+ })
46
+ )
47
+ );
48
+ allowedPermissions = permissionNames.filter(
49
+ (_, i) => responses[i].result === "ALLOW"
50
+ );
51
+ }
52
+ return {
53
+ featureFlags: getActiveFeatureFlags(),
54
+ permissions: allowedPermissions
55
+ };
56
+ }
57
+ return {
58
+ getImmediate,
59
+ load
60
+ };
61
+ }
62
+ function collectPredicateReferences(nodes) {
63
+ const featureFlags = /* @__PURE__ */ new Set();
64
+ const permissions = /* @__PURE__ */ new Set();
65
+ for (const node of nodes) {
66
+ if (node.spec.if === void 0) {
67
+ continue;
68
+ }
69
+ for (const name of extractFeatureFlagNames(node.spec.if)) {
70
+ featureFlags.add(name);
71
+ }
72
+ for (const name of extractPermissionNames(node.spec.if)) {
73
+ permissions.add(name);
74
+ }
75
+ }
76
+ return {
77
+ featureFlags: Array.from(featureFlags),
78
+ permissions: Array.from(permissions)
79
+ };
80
+ }
81
+ function extractFeatureFlagNames(predicate) {
82
+ return extractPredicateKeyNames(predicate, "featureFlags");
83
+ }
84
+ function extractPermissionNames(predicate) {
85
+ return extractPredicateKeyNames(predicate, "permissions");
86
+ }
87
+ function extractPredicateKeyNames(predicate, key) {
88
+ if (typeof predicate !== "object" || predicate === null) {
89
+ return [];
90
+ }
91
+ const obj = predicate;
92
+ if (Array.isArray(obj.$all)) {
93
+ return obj.$all.flatMap(
94
+ (p) => extractPredicateKeyNames(p, key)
95
+ );
96
+ }
97
+ if (Array.isArray(obj.$any)) {
98
+ return obj.$any.flatMap(
99
+ (p) => extractPredicateKeyNames(p, key)
100
+ );
101
+ }
102
+ if (obj.$not !== void 0) {
103
+ return extractPredicateKeyNames(obj.$not, key);
104
+ }
105
+ const value = obj[key];
106
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
107
+ const contains = value.$contains;
108
+ if (typeof contains === "string") {
109
+ return [contains];
110
+ }
111
+ }
112
+ return [];
113
+ }
114
+
115
+ export { EMPTY_PREDICATE_CONTEXT, collectPredicateReferences, createPredicateContextLoader, localPermissionApiRef };
116
+ //# sourceMappingURL=predicates.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.esm.js","sources":["../../src/wiring/predicates.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ApiHolder,\n createApiRef,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { FilterPredicate } from '@backstage/filter-predicates';\nimport type {\n EvaluatePermissionRequest,\n EvaluatePermissionResponse,\n} from '@backstage/plugin-permission-common';\n\nexport type ExtensionPredicateContext = {\n featureFlags: string[];\n permissions: string[];\n};\n\nexport const EMPTY_PREDICATE_CONTEXT: ExtensionPredicateContext = {\n featureFlags: [],\n permissions: [],\n};\n\n// Minimal local permission API interface to avoid a dependency on @backstage/plugin-permission-react\ntype MinimalPermissionApi = {\n authorize(\n request: EvaluatePermissionRequest,\n ): Promise<EvaluatePermissionResponse>;\n};\n\nexport const localPermissionApiRef = createApiRef<MinimalPermissionApi>({\n id: 'plugin.permission.api',\n});\n\nexport function createPredicateContextLoader(options: {\n apis: ApiHolder;\n predicateReferences: ExtensionPredicateContext;\n}) {\n function getActiveFeatureFlags() {\n const featureFlagsApi = options.apis.get(featureFlagsApiRef);\n if (!featureFlagsApi) {\n return [];\n }\n\n return options.predicateReferences.featureFlags.filter(name =>\n featureFlagsApi.isActive(name),\n );\n }\n\n function getImmediate(): ExtensionPredicateContext | undefined {\n if (options.predicateReferences.permissions.length > 0) {\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n return undefined;\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: [],\n };\n }\n\n async function load() {\n const immediatePredicateContext = getImmediate();\n if (immediatePredicateContext) {\n return immediatePredicateContext;\n }\n\n let allowedPermissions: string[] = [];\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n const permissionNames = options.predicateReferences.permissions;\n const responses = await Promise.all(\n permissionNames.map(name =>\n permissionApi.authorize({\n permission: { name, type: 'basic', attributes: {} },\n }),\n ),\n );\n allowedPermissions = permissionNames.filter(\n (_, i) => responses[i].result === 'ALLOW',\n );\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: allowedPermissions,\n };\n }\n\n return {\n getImmediate,\n load,\n };\n}\n\nexport function collectPredicateReferences(\n nodes: Iterable<{ spec: { if?: FilterPredicate } }>,\n): ExtensionPredicateContext {\n const featureFlags = new Set<string>();\n const permissions = new Set<string>();\n\n for (const node of nodes) {\n if (node.spec.if === undefined) {\n continue;\n }\n\n for (const name of extractFeatureFlagNames(node.spec.if)) {\n featureFlags.add(name);\n }\n for (const name of extractPermissionNames(node.spec.if)) {\n permissions.add(name);\n }\n }\n\n return {\n featureFlags: Array.from(featureFlags),\n permissions: Array.from(permissions),\n };\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `featureFlags: { $contains: '...' }` expressions. This lets us call\n * `isActive()` only for the flags that are actually used in predicates rather\n * than fetching the full registered-flag list.\n */\nfunction extractFeatureFlagNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'featureFlags');\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `permissions: { $contains: '...' }` expressions. This lets us issue a\n * single batched authorize call for only the permissions actually referenced.\n */\nfunction extractPermissionNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'permissions');\n}\n\nfunction extractPredicateKeyNames(\n predicate: FilterPredicate,\n key: string,\n): string[] {\n if (typeof predicate !== 'object' || predicate === null) {\n return [];\n }\n const obj = predicate as Record<string, unknown>;\n if (Array.isArray(obj.$all)) {\n return (obj.$all as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (Array.isArray(obj.$any)) {\n return (obj.$any as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (obj.$not !== undefined) {\n return extractPredicateKeyNames(obj.$not as FilterPredicate, key);\n }\n const value = obj[key];\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const contains = (value as Record<string, unknown>).$contains;\n if (typeof contains === 'string') {\n return [contains];\n }\n }\n return [];\n}\n"],"names":[],"mappings":"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"}