@backstage/plugin-app 0.3.1-next.2 → 0.3.2-next.0

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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # @backstage/plugin-app
2
2
 
3
+ ## 0.3.2-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/plugin-permission-react@0.4.38-next.0
9
+ - @backstage/core-plugin-api@1.11.2-next.0
10
+ - @backstage/core-components@0.18.3-next.0
11
+ - @backstage/frontend-plugin-api@0.12.2-next.0
12
+ - @backstage/integration-react@1.2.12-next.0
13
+ - @backstage/theme@0.7.0
14
+ - @backstage/types@1.2.2
15
+ - @backstage/version-bridge@1.0.11
16
+
17
+ ## 0.3.1
18
+
19
+ ### Patch Changes
20
+
21
+ - ae1dad0: Fixed an issue that caused the `NotFound` page to not render correctly when a Page was mounted at `/`.
22
+ - Updated dependencies
23
+ - @backstage/core-components@0.18.2
24
+ - @backstage/frontend-plugin-api@0.12.1
25
+ - @backstage/theme@0.7.0
26
+ - @backstage/core-plugin-api@1.11.1
27
+ - @backstage/integration-react@1.2.11
28
+ - @backstage/plugin-permission-react@0.4.37
29
+
3
30
  ## 0.3.1-next.2
4
31
 
5
32
  ### Patch Changes
@@ -16,10 +16,13 @@ const AppRoutes = createExtension({
16
16
  factory({ inputs }) {
17
17
  const Routes = () => {
18
18
  const element = useRoutes([
19
- ...inputs.routes.map((route) => ({
20
- path: `${route.get(coreExtensionData.routePath).replace(/\/$/, "")}/*`,
21
- element: route.get(coreExtensionData.reactElement)
22
- })),
19
+ ...inputs.routes.map((route) => {
20
+ const routePath = route.get(coreExtensionData.routePath);
21
+ return {
22
+ path: routePath === "/" ? routePath : `${routePath.replace(/\/$/, "")}/*`,
23
+ element: route.get(coreExtensionData.reactElement)
24
+ };
25
+ }),
23
26
  {
24
27
  path: "*",
25
28
  element: /* @__PURE__ */ jsx(NotFoundErrorPage, {})
@@ -1 +1 @@
1
- {"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { useRoutes } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n const Routes = () => {\n const element = useRoutes([\n ...inputs.routes.map(route => ({\n path: `${route\n .get(coreExtensionData.routePath)\n .replace(/\\/$/, '')}/*`,\n element: route.get(coreExtensionData.reactElement),\n })),\n {\n path: '*',\n element: <NotFoundErrorPage />,\n },\n ]);\n\n return element;\n };\n\n return [coreExtensionData.reactElement(<Routes />)];\n },\n});\n"],"names":[],"mappings":";;;;AAwBO,MAAM,YAAY,eAAA,CAAgB;AAAA,EACvC,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,SAAA,EAAU;AAAA,EAC/C,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB;AAAA,MAC3B,iBAAA,CAAkB,SAAA;AAAA,MAClB,iBAAA,CAAkB,SAAS,QAAA,EAAS;AAAA,MACpC,iBAAA,CAAkB;AAAA,KACnB;AAAA,GACH;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AAClB,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,UAAU,SAAA,CAAU;AAAA,QACxB,GAAG,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,MAAU;AAAA,UAC7B,IAAA,EAAM,CAAA,EAAG,KAAA,CACN,GAAA,CAAI,iBAAA,CAAkB,SAAS,CAAA,CAC/B,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,UACrB,OAAA,EAAS,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,YAAY;AAAA,SACnD,CAAE,CAAA;AAAA,QACF;AAAA,UACE,IAAA,EAAM,GAAA;AAAA,UACN,OAAA,sBAAU,iBAAA,EAAA,EAAkB;AAAA;AAC9B,OACD,CAAA;AAED,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,CAAC,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,MAAA,EAAA,EAAO,CAAE,CAAC,CAAA;AAAA,EACpD;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { useRoutes } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n const Routes = () => {\n const element = useRoutes([\n ...inputs.routes.map(route => {\n const routePath = route.get(coreExtensionData.routePath);\n\n return {\n path:\n routePath === '/'\n ? routePath\n : `${routePath.replace(/\\/$/, '')}/*`,\n\n element: route.get(coreExtensionData.reactElement),\n };\n }),\n {\n path: '*',\n element: <NotFoundErrorPage />,\n },\n ]);\n\n return element;\n };\n\n return [coreExtensionData.reactElement(<Routes />)];\n },\n});\n"],"names":[],"mappings":";;;;AAwBO,MAAM,YAAY,eAAA,CAAgB;AAAA,EACvC,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,SAAA,EAAU;AAAA,EAC/C,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB;AAAA,MAC3B,iBAAA,CAAkB,SAAA;AAAA,MAClB,iBAAA,CAAkB,SAAS,QAAA,EAAS;AAAA,MACpC,iBAAA,CAAkB;AAAA,KACnB;AAAA,GACH;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AAClB,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,UAAU,SAAA,CAAU;AAAA,QACxB,GAAG,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAC5B,UAAA,MAAM,SAAA,GAAY,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,SAAS,CAAA;AAEvD,UAAA,OAAO;AAAA,YACL,IAAA,EACE,cAAc,GAAA,GACV,SAAA,GACA,GAAG,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,YAErC,OAAA,EAAS,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,YAAY;AAAA,WACnD;AAAA,QACF,CAAC,CAAA;AAAA,QACD;AAAA,UACE,IAAA,EAAM,GAAA;AAAA,UACN,OAAA,sBAAU,iBAAA,EAAA,EAAkB;AAAA;AAC9B,OACD,CAAA;AAED,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,CAAC,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,MAAA,EAAA,EAAO,CAAE,CAAC,CAAA;AAAA,EACpD;AACF,CAAC;;;;"}
@@ -1,4 +1,6 @@
1
1
  class ErrorAlerter {
2
+ alertApi;
3
+ errorApi;
2
4
  constructor(alertApi, errorApi) {
3
5
  this.alertApi = alertApi;
4
6
  this.errorApi = errorApi;
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorAlerter.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts"],"sourcesContent":["/*\n * Copyright 2020 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 */\nimport {\n ErrorApi,\n ErrorApiError,\n ErrorApiErrorContext,\n AlertApi,\n} from '@backstage/core-plugin-api';\n\n/**\n * Decorates an ErrorApi by also forwarding error messages\n * to the alertApi with an 'error' severity.\n *\n * @public\n */\nexport class ErrorAlerter implements ErrorApi {\n constructor(\n private readonly alertApi: AlertApi,\n private readonly errorApi: ErrorApi,\n ) {}\n\n post(error: ErrorApiError, context?: ErrorApiErrorContext) {\n if (!context?.hidden) {\n this.alertApi.post({ message: error.message, severity: 'error' });\n }\n\n return this.errorApi.post(error, context);\n }\n\n error$() {\n return this.errorApi.error$();\n }\n}\n"],"names":[],"mappings":"AA4BO,MAAM,YAAA,CAAiC;AAAA,EAC5C,WAAA,CACmB,UACA,QAAA,EACjB;AAFiB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA,EAChB;AAAA,EAEH,IAAA,CAAK,OAAsB,OAAA,EAAgC;AACzD,IAAA,IAAI,CAAC,SAAS,MAAA,EAAQ;AACpB,MAAA,IAAA,CAAK,QAAA,CAAS,KAAK,EAAE,OAAA,EAAS,MAAM,OAAA,EAAS,QAAA,EAAU,SAAS,CAAA;AAAA,IAClE;AAEA,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,KAAA,EAAO,OAAO,CAAA;AAAA,EAC1C;AAAA,EAEA,MAAA,GAAS;AACP,IAAA,OAAO,IAAA,CAAK,SAAS,MAAA,EAAO;AAAA,EAC9B;AACF;;;;"}
1
+ {"version":3,"file":"ErrorAlerter.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts"],"sourcesContent":["/*\n * Copyright 2020 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 */\nimport {\n ErrorApi,\n ErrorApiError,\n ErrorApiErrorContext,\n AlertApi,\n} from '@backstage/core-plugin-api';\n\n/**\n * Decorates an ErrorApi by also forwarding error messages\n * to the alertApi with an 'error' severity.\n *\n * @public\n */\nexport class ErrorAlerter implements ErrorApi {\n private readonly alertApi: AlertApi;\n private readonly errorApi: ErrorApi;\n\n constructor(alertApi: AlertApi, errorApi: ErrorApi) {\n this.alertApi = alertApi;\n this.errorApi = errorApi;\n }\n\n post(error: ErrorApiError, context?: ErrorApiErrorContext) {\n if (!context?.hidden) {\n this.alertApi.post({ message: error.message, severity: 'error' });\n }\n\n return this.errorApi.post(error, context);\n }\n\n error$() {\n return this.errorApi.error$();\n }\n}\n"],"names":[],"mappings":"AA4BO,MAAM,YAAA,CAAiC;AAAA,EAC3B,QAAA;AAAA,EACA,QAAA;AAAA,EAEjB,WAAA,CAAY,UAAoB,QAAA,EAAoB;AAClD,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AAAA,EAEA,IAAA,CAAK,OAAsB,OAAA,EAAgC;AACzD,IAAA,IAAI,CAAC,SAAS,MAAA,EAAQ;AACpB,MAAA,IAAA,CAAK,QAAA,CAAS,KAAK,EAAE,OAAA,EAAS,MAAM,OAAA,EAAS,QAAA,EAAU,SAAS,CAAA;AAAA,IAClE;AAEA,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,KAAA,EAAO,OAAO,CAAA;AAAA,EAC1C;AAAA,EAEA,MAAA,GAAS;AACP,IAAA,OAAO,IAAA,CAAK,SAAS,MAAA,EAAO;AAAA,EAC9B;AACF;;;;"}
@@ -1,10 +1,4 @@
1
1
  class IdentityAuthInjectorFetchMiddleware {
2
- constructor(identityApi, allowUrl, headerName, headerValue) {
3
- this.identityApi = identityApi;
4
- this.allowUrl = allowUrl;
5
- this.headerName = headerName;
6
- this.headerValue = headerValue;
7
- }
8
2
  static create(options) {
9
3
  const matcher = buildMatcher(options);
10
4
  const headerName = options.header?.name || "authorization";
@@ -30,6 +24,16 @@ class IdentityAuthInjectorFetchMiddleware {
30
24
  );
31
25
  });
32
26
  }
27
+ identityApi;
28
+ allowUrl;
29
+ headerName;
30
+ headerValue;
31
+ constructor(identityApi, allowUrl, headerName, headerValue) {
32
+ this.identityApi = identityApi;
33
+ this.allowUrl = allowUrl;
34
+ this.headerName = headerName;
35
+ this.headerValue = headerValue;
36
+ }
33
37
  apply(next) {
34
38
  return async (input, init) => {
35
39
  const request = new Request(input, init);
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityAuthInjectorFetchMiddleware.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { Config } from '@backstage/config';\nimport { IdentityApi } from '@backstage/core-plugin-api';\nimport { FetchMiddleware } from './types';\n\n/**\n * A fetch middleware, which injects a Backstage token header when the user is\n * signed in.\n */\nexport class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {\n static create(options: {\n identityApi: IdentityApi;\n config?: Config;\n urlPrefixAllowlist?: string[];\n allowUrl?: (url: string) => boolean;\n header?: {\n name: string;\n value: (backstageToken: string) => string;\n };\n }): IdentityAuthInjectorFetchMiddleware {\n const matcher = buildMatcher(options);\n const headerName = options.header?.name || 'authorization';\n const headerValue = options.header?.value || (token => `Bearer ${token}`);\n\n return new IdentityAuthInjectorFetchMiddleware(\n options.identityApi,\n matcher,\n headerName,\n headerValue,\n );\n }\n\n /**\n * Returns an array of plugin URL prefixes derived from the static `discovery`\n * configuration, to be used as `urlPrefixAllowlist` option of {@link create}.\n */\n static getDiscoveryUrlPrefixes(config: Config): string[] {\n const endpointConfigs =\n config.getOptionalConfigArray('discovery.endpoints') || [];\n return endpointConfigs.flatMap(c => {\n const target =\n typeof c.get('target') === 'object'\n ? c.getString('target.external')\n : c.getString('target');\n const plugins = c.getStringArray('plugins');\n return plugins.map(pluginId =>\n target.replace(/\\{\\{\\s*pluginId\\s*\\}\\}/g, pluginId),\n );\n });\n }\n\n constructor(\n public readonly identityApi: IdentityApi,\n public readonly allowUrl: (url: string) => boolean,\n public readonly headerName: string,\n public readonly headerValue: (pluginId: string) => string,\n ) {}\n\n apply(next: typeof fetch): typeof fetch {\n return async (input, init) => {\n // Skip this middleware if the header already exists, or if the URL\n // doesn't match any of the allowlist items, or if there was no token.\n // NOTE(freben): The \"as any\" casts here and below are because of subtle\n // undici type differences that happened in a node types bump. Those are\n // immaterial to the code at hand at runtime, as the global fetch and\n // Request are always taken from the same place.\n const request = new Request(input as any, init);\n const { token } = await this.identityApi.getCredentials();\n if (\n request.headers.get(this.headerName) ||\n typeof token !== 'string' ||\n !token ||\n !this.allowUrl(request.url)\n ) {\n return next(input as any, init);\n }\n\n request.headers.set(this.headerName, this.headerValue(token));\n return next(request);\n };\n }\n}\n\nfunction buildMatcher(options: {\n config?: Config;\n urlPrefixAllowlist?: string[];\n allowUrl?: (url: string) => boolean;\n}): (url: string) => boolean {\n if (options.allowUrl) {\n return options.allowUrl;\n } else if (options.urlPrefixAllowlist) {\n return buildPrefixMatcher(options.urlPrefixAllowlist);\n } else if (options.config) {\n return buildPrefixMatcher([\n options.config.getString('backend.baseUrl'),\n ...IdentityAuthInjectorFetchMiddleware.getDiscoveryUrlPrefixes(\n options.config,\n ),\n ]);\n }\n return () => false;\n}\n\nfunction buildPrefixMatcher(prefixes: string[]): (url: string) => boolean {\n const trimmedPrefixes = prefixes.map(prefix => prefix.replace(/\\/$/, ''));\n return url =>\n trimmedPrefixes.some(\n prefix => url === prefix || url.startsWith(`${prefix}/`),\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"IdentityAuthInjectorFetchMiddleware.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { Config } from '@backstage/config';\nimport { IdentityApi } from '@backstage/core-plugin-api';\nimport { FetchMiddleware } from './types';\n\n/**\n * A fetch middleware, which injects a Backstage token header when the user is\n * signed in.\n */\nexport class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {\n static create(options: {\n identityApi: IdentityApi;\n config?: Config;\n urlPrefixAllowlist?: string[];\n allowUrl?: (url: string) => boolean;\n header?: {\n name: string;\n value: (backstageToken: string) => string;\n };\n }): IdentityAuthInjectorFetchMiddleware {\n const matcher = buildMatcher(options);\n const headerName = options.header?.name || 'authorization';\n const headerValue = options.header?.value || (token => `Bearer ${token}`);\n\n return new IdentityAuthInjectorFetchMiddleware(\n options.identityApi,\n matcher,\n headerName,\n headerValue,\n );\n }\n\n /**\n * Returns an array of plugin URL prefixes derived from the static `discovery`\n * configuration, to be used as `urlPrefixAllowlist` option of {@link create}.\n */\n static getDiscoveryUrlPrefixes(config: Config): string[] {\n const endpointConfigs =\n config.getOptionalConfigArray('discovery.endpoints') || [];\n return endpointConfigs.flatMap(c => {\n const target =\n typeof c.get('target') === 'object'\n ? c.getString('target.external')\n : c.getString('target');\n const plugins = c.getStringArray('plugins');\n return plugins.map(pluginId =>\n target.replace(/\\{\\{\\s*pluginId\\s*\\}\\}/g, pluginId),\n );\n });\n }\n\n public readonly identityApi: IdentityApi;\n public readonly allowUrl: (url: string) => boolean;\n public readonly headerName: string;\n public readonly headerValue: (pluginId: string) => string;\n\n constructor(\n identityApi: IdentityApi,\n allowUrl: (url: string) => boolean,\n headerName: string,\n headerValue: (pluginId: string) => string,\n ) {\n this.identityApi = identityApi;\n this.allowUrl = allowUrl;\n this.headerName = headerName;\n this.headerValue = headerValue;\n }\n\n apply(next: typeof fetch): typeof fetch {\n return async (input, init) => {\n // Skip this middleware if the header already exists, or if the URL\n // doesn't match any of the allowlist items, or if there was no token.\n // NOTE(freben): The \"as any\" casts here and below are because of subtle\n // undici type differences that happened in a node types bump. Those are\n // immaterial to the code at hand at runtime, as the global fetch and\n // Request are always taken from the same place.\n const request = new Request(input as any, init);\n const { token } = await this.identityApi.getCredentials();\n if (\n request.headers.get(this.headerName) ||\n typeof token !== 'string' ||\n !token ||\n !this.allowUrl(request.url)\n ) {\n return next(input as any, init);\n }\n\n request.headers.set(this.headerName, this.headerValue(token));\n return next(request);\n };\n }\n}\n\nfunction buildMatcher(options: {\n config?: Config;\n urlPrefixAllowlist?: string[];\n allowUrl?: (url: string) => boolean;\n}): (url: string) => boolean {\n if (options.allowUrl) {\n return options.allowUrl;\n } else if (options.urlPrefixAllowlist) {\n return buildPrefixMatcher(options.urlPrefixAllowlist);\n } else if (options.config) {\n return buildPrefixMatcher([\n options.config.getString('backend.baseUrl'),\n ...IdentityAuthInjectorFetchMiddleware.getDiscoveryUrlPrefixes(\n options.config,\n ),\n ]);\n }\n return () => false;\n}\n\nfunction buildPrefixMatcher(prefixes: string[]): (url: string) => boolean {\n const trimmedPrefixes = prefixes.map(prefix => prefix.replace(/\\/$/, ''));\n return url =>\n trimmedPrefixes.some(\n prefix => url === prefix || url.startsWith(`${prefix}/`),\n );\n}\n"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@ import ObservableImpl from 'zen-observable';
2
2
 
3
3
  const buckets = /* @__PURE__ */ new Map();
4
4
  class WebStorage {
5
+ namespace;
6
+ errorApi;
5
7
  constructor(namespace, errorApi) {
6
8
  this.namespace = namespace;
7
9
  this.errorApi = errorApi;
@@ -1 +1 @@
1
- {"version":3,"file":"WebStorage.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 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 StorageApi,\n StorageValueSnapshot,\n ErrorApi,\n} from '@backstage/core-plugin-api';\nimport { JsonValue, Observable } from '@backstage/types';\nimport ObservableImpl from 'zen-observable';\n\nexport const buckets = new Map<string, WebStorage>();\n\n/**\n * An implementation of the storage API, that uses the browser's local storage.\n *\n * @public\n */\nexport class WebStorage implements StorageApi {\n constructor(\n private readonly namespace: string,\n private readonly errorApi: ErrorApi,\n ) {}\n\n private static hasSubscribed = false;\n\n static create(options: {\n errorApi: ErrorApi;\n namespace?: string;\n }): WebStorage {\n return new WebStorage(options.namespace ?? '', options.errorApi);\n }\n\n private static addStorageEventListener() {\n window.addEventListener('storage', event => {\n for (const [bucketPath, webStorage] of buckets.entries()) {\n if (event.key?.startsWith(bucketPath)) {\n webStorage.handleStorageChange(event.key);\n }\n }\n });\n }\n\n get<T>(key: string): T | undefined {\n return this.snapshot(key).value as T | undefined;\n }\n\n snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {\n let value = undefined;\n let presence: 'present' | 'absent' = 'absent';\n try {\n const item = localStorage.getItem(this.getKeyName(key));\n if (item) {\n value = JSON.parse(item, (_key, val) => {\n if (typeof val === 'object' && val !== null) {\n Object.freeze(val);\n }\n return val;\n });\n presence = 'present';\n }\n } catch (e) {\n this.errorApi.post(\n new Error(`Error when parsing JSON config from storage for: ${key}`),\n );\n }\n return { key, value, presence };\n }\n\n forBucket(name: string): WebStorage {\n const bucketPath = `${this.namespace}/${name}`;\n if (!buckets.has(bucketPath)) {\n buckets.set(bucketPath, new WebStorage(bucketPath, this.errorApi));\n }\n return buckets.get(bucketPath)!;\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n localStorage.setItem(this.getKeyName(key), JSON.stringify(data));\n this.notifyChanges(key);\n }\n\n async remove(key: string): Promise<void> {\n localStorage.removeItem(this.getKeyName(key));\n this.notifyChanges(key);\n }\n\n observe$<T extends JsonValue>(\n key: string,\n ): Observable<StorageValueSnapshot<T>> {\n if (!WebStorage.hasSubscribed) {\n WebStorage.addStorageEventListener();\n WebStorage.hasSubscribed = true;\n }\n return this.observable.filter(({ key: messageKey }) => messageKey === key);\n }\n\n private handleStorageChange(eventKey: StorageEvent['key']) {\n if (!eventKey?.startsWith(this.namespace)) {\n return;\n }\n // Grab the part of this key that is local to this bucket\n const trimmedKey = eventKey?.slice(`${this.namespace}/`.length);\n\n // If the key still contains a slash, it means it's a sub-bucket\n if (!trimmedKey.includes('/')) {\n this.notifyChanges(decodeURIComponent(trimmedKey));\n }\n }\n\n private getKeyName(key: string) {\n return `${this.namespace}/${encodeURIComponent(key)}`;\n }\n\n private notifyChanges(key: string) {\n const snapshot = this.snapshot(key);\n for (const subscription of this.subscribers) {\n subscription.next(snapshot);\n }\n }\n\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly observable = new ObservableImpl<\n StorageValueSnapshot<JsonValue>\n >(subscriber => {\n this.subscribers.add(subscriber);\n return () => {\n this.subscribers.delete(subscriber);\n };\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WebStorage.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 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 StorageApi,\n StorageValueSnapshot,\n ErrorApi,\n} from '@backstage/core-plugin-api';\nimport { JsonValue, Observable } from '@backstage/types';\nimport ObservableImpl from 'zen-observable';\n\nexport const buckets = new Map<string, WebStorage>();\n\n/**\n * An implementation of the storage API, that uses the browser's local storage.\n *\n * @public\n */\nexport class WebStorage implements StorageApi {\n private readonly namespace: string;\n private readonly errorApi: ErrorApi;\n\n constructor(namespace: string, errorApi: ErrorApi) {\n this.namespace = namespace;\n this.errorApi = errorApi;\n }\n\n private static hasSubscribed = false;\n\n static create(options: {\n errorApi: ErrorApi;\n namespace?: string;\n }): WebStorage {\n return new WebStorage(options.namespace ?? '', options.errorApi);\n }\n\n private static addStorageEventListener() {\n window.addEventListener('storage', event => {\n for (const [bucketPath, webStorage] of buckets.entries()) {\n if (event.key?.startsWith(bucketPath)) {\n webStorage.handleStorageChange(event.key);\n }\n }\n });\n }\n\n get<T>(key: string): T | undefined {\n return this.snapshot(key).value as T | undefined;\n }\n\n snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {\n let value = undefined;\n let presence: 'present' | 'absent' = 'absent';\n try {\n const item = localStorage.getItem(this.getKeyName(key));\n if (item) {\n value = JSON.parse(item, (_key, val) => {\n if (typeof val === 'object' && val !== null) {\n Object.freeze(val);\n }\n return val;\n });\n presence = 'present';\n }\n } catch (e) {\n this.errorApi.post(\n new Error(`Error when parsing JSON config from storage for: ${key}`),\n );\n }\n return { key, value, presence };\n }\n\n forBucket(name: string): WebStorage {\n const bucketPath = `${this.namespace}/${name}`;\n if (!buckets.has(bucketPath)) {\n buckets.set(bucketPath, new WebStorage(bucketPath, this.errorApi));\n }\n return buckets.get(bucketPath)!;\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n localStorage.setItem(this.getKeyName(key), JSON.stringify(data));\n this.notifyChanges(key);\n }\n\n async remove(key: string): Promise<void> {\n localStorage.removeItem(this.getKeyName(key));\n this.notifyChanges(key);\n }\n\n observe$<T extends JsonValue>(\n key: string,\n ): Observable<StorageValueSnapshot<T>> {\n if (!WebStorage.hasSubscribed) {\n WebStorage.addStorageEventListener();\n WebStorage.hasSubscribed = true;\n }\n return this.observable.filter(({ key: messageKey }) => messageKey === key);\n }\n\n private handleStorageChange(eventKey: StorageEvent['key']) {\n if (!eventKey?.startsWith(this.namespace)) {\n return;\n }\n // Grab the part of this key that is local to this bucket\n const trimmedKey = eventKey?.slice(`${this.namespace}/`.length);\n\n // If the key still contains a slash, it means it's a sub-bucket\n if (!trimmedKey.includes('/')) {\n this.notifyChanges(decodeURIComponent(trimmedKey));\n }\n }\n\n private getKeyName(key: string) {\n return `${this.namespace}/${encodeURIComponent(key)}`;\n }\n\n private notifyChanges(key: string) {\n const snapshot = this.snapshot(key);\n for (const subscription of this.subscribers) {\n subscription.next(snapshot);\n }\n }\n\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly observable = new ObservableImpl<\n StorageValueSnapshot<JsonValue>\n >(subscriber => {\n this.subscribers.add(subscriber);\n return () => {\n this.subscribers.delete(subscriber);\n };\n });\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  var name = "@backstage/plugin-app";
2
- var version = "0.3.1-next.2";
2
+ var version = "0.3.2-next.0";
3
3
  var backstage = {
4
4
  role: "frontend-plugin",
5
5
  pluginId: "app",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-app",
3
- "version": "0.3.1-next.2",
3
+ "version": "0.3.2-next.0",
4
4
  "backstage": {
5
5
  "role": "frontend-plugin",
6
6
  "pluginId": "app",
@@ -62,12 +62,12 @@
62
62
  "test": "backstage-cli package test"
63
63
  },
64
64
  "dependencies": {
65
- "@backstage/core-components": "0.18.2-next.2",
66
- "@backstage/core-plugin-api": "1.11.1-next.0",
67
- "@backstage/frontend-plugin-api": "0.12.1-next.1",
68
- "@backstage/integration-react": "1.2.11-next.1",
69
- "@backstage/plugin-permission-react": "0.4.37-next.0",
70
- "@backstage/theme": "0.6.9-next.0",
65
+ "@backstage/core-components": "0.18.3-next.0",
66
+ "@backstage/core-plugin-api": "1.11.2-next.0",
67
+ "@backstage/frontend-plugin-api": "0.12.2-next.0",
68
+ "@backstage/integration-react": "1.2.12-next.0",
69
+ "@backstage/plugin-permission-react": "0.4.38-next.0",
70
+ "@backstage/theme": "0.7.0",
71
71
  "@backstage/types": "1.2.2",
72
72
  "@backstage/version-bridge": "1.0.11",
73
73
  "@material-ui/core": "^4.9.13",
@@ -78,11 +78,11 @@
78
78
  "zod": "^3.22.4"
79
79
  },
80
80
  "devDependencies": {
81
- "@backstage/cli": "0.34.4-next.2",
82
- "@backstage/dev-utils": "1.1.15-next.2",
83
- "@backstage/frontend-defaults": "0.3.2-next.1",
84
- "@backstage/frontend-test-utils": "0.3.7-next.1",
85
- "@backstage/test-utils": "1.7.12-next.1",
81
+ "@backstage/cli": "0.34.5-next.0",
82
+ "@backstage/dev-utils": "1.1.17-next.0",
83
+ "@backstage/frontend-defaults": "0.3.3-next.0",
84
+ "@backstage/frontend-test-utils": "0.4.1-next.0",
85
+ "@backstage/test-utils": "1.7.13-next.0",
86
86
  "@testing-library/jest-dom": "^6.0.0",
87
87
  "@testing-library/react": "^16.0.0",
88
88
  "@testing-library/user-event": "^14.0.0",