diff --git a/.changeset/khaki-penguins-grin.md b/.changeset/khaki-penguins-grin.md new file mode 100644 index 0000000..32f1263 --- /dev/null +++ b/.changeset/khaki-penguins-grin.md @@ -0,0 +1,5 @@ +--- +"@weissaufschwarz/mitthooks": patch +--- + +BUGFIX: ignore casing of used webhook signature algorithm diff --git a/.changeset/shaggy-tigers-itch.md b/.changeset/shaggy-tigers-itch.md new file mode 100644 index 0000000..90af768 --- /dev/null +++ b/.changeset/shaggy-tigers-itch.md @@ -0,0 +1,5 @@ +--- +"@weissaufschwarz/mitthooks-nextjs": patch +--- + +BUGFIX: await request headers of nextjs webhook handler. diff --git a/.changeset/silly-tools-reflect.md b/.changeset/silly-tools-reflect.md new file mode 100644 index 0000000..c6061b6 --- /dev/null +++ b/.changeset/silly-tools-reflect.md @@ -0,0 +1,5 @@ +--- +"@weissaufschwarz/mitthooks": patch +--- + +FEATURE: allow webhook handler without promises diff --git a/packages/mitthooks-nextjs/src/index.ts b/packages/mitthooks-nextjs/src/index.ts index 032b351..022f0f8 100644 --- a/packages/mitthooks-nextjs/src/index.ts +++ b/packages/mitthooks-nextjs/src/index.ts @@ -28,7 +28,8 @@ export class NextJSWebhookHandler { }; private async getWebhookContent(request: Request): Promise { - const headersList = headers(); + // eslint-disable-next-line @typescript-eslint/await-thenable + const headersList = await headers(); const signatureSerial = this.getHeader( headersList, "x-marketplace-signature-serial", diff --git a/packages/mitthooks/src/examples/separateWebhookHandler.ts b/packages/mitthooks/src/examples/separateWebhookHandler.ts index 77c25d4..42b6492 100644 --- a/packages/mitthooks/src/examples/separateWebhookHandler.ts +++ b/packages/mitthooks/src/examples/separateWebhookHandler.ts @@ -52,19 +52,22 @@ function createCustomSeparateWebhookHandler( .build(); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars function createCustomSeparateWebhookHandlerWithHandlerForAdded( extensionStorage: ExtensionStorage, extensionId: string, ): SeparateWebhookHandlers { const handlers = new SeparateWebhookHandlerFactory(extensionStorage, extensionId).build(); - handlers.ExtensionAddedToContext = WebhookHandlerChain + const extendedChain = WebhookHandlerChain .fromHandlerFunctions( handlers.ExtensionAddedToContext, - async (webhookContent) => { + () => { console.log("This is only gonna be called for ExtensionAddedToContext"); }, - ).handleWebhook; + ) + + handlers.ExtensionAddedToContext = extendedChain.handleWebhook.bind(extendedChain); return handlers; } diff --git a/packages/mitthooks/src/factory/base.ts b/packages/mitthooks/src/factory/base.ts index ec8982e..43f326b 100644 --- a/packages/mitthooks/src/factory/base.ts +++ b/packages/mitthooks/src/factory/base.ts @@ -20,6 +20,7 @@ export abstract class BaseWebhookHandlerFactory { protected baseHandlerChain: WebhookHandlerChain = new WebhookHandlerChain(); protected handlerChainSuffix: WebhookHandler[] = []; protected verifyWebhookSignature = true; + protected mittwaldAPIURL: string | undefined; protected constructor(extensionStorage: ExtensionStorage, extensionID: string) { this.extensionStorage = extensionStorage; @@ -36,6 +37,11 @@ export abstract class BaseWebhookHandlerFactory { return this; } + public withMittwaldAPIURL(url: string): this { + this.mittwaldAPIURL = url; + return this; + } + public withWebhookHandlerPrefix( ...additionalHandlers: WebhookHandler[] ): this { @@ -59,7 +65,7 @@ export abstract class BaseWebhookHandlerFactory { protected buildWebhookVerifier(): WebhookVerifier { const publicKeyProvider = new CachingPublicKeyProvider( - APIPublicKeyProvider.newWithUnauthenticatedAPIClient(), + APIPublicKeyProvider.newWithUnauthenticatedAPIClient(this.mittwaldAPIURL) ); return new WebhookVerifier(this.logger, publicKeyProvider); @@ -75,13 +81,13 @@ export abstract class BaseWebhookHandlerFactory { this.baseHandlerChain = this.baseHandlerChain.withAdditionalHandlers( new LoggingWebhookHandler(this.logger), - new ExtensionIDVerificationWebhookHandler(this.extensionID), + new ExtensionIDVerificationWebhookHandler(this.extensionID, this.logger), ); if (this.verifyWebhookSignature) { this.baseHandlerChain = this.baseHandlerChain.withAdditionalHandlers( - new VerifyingWebhookHandler(webhookVerifier), + new VerifyingWebhookHandler(webhookVerifier, this.logger), ); } } diff --git a/packages/mitthooks/src/factory/separate.ts b/packages/mitthooks/src/factory/separate.ts index 927a623..6fc70b5 100644 --- a/packages/mitthooks/src/factory/separate.ts +++ b/packages/mitthooks/src/factory/separate.ts @@ -28,25 +28,25 @@ export class SeparateWebhookHandlerFactory extends BaseWebhookHandlerFactory { return { [extensionAddedToContextKind]: this.baseHandlerChain .withAdditionalHandlers( - new AddedToContextWebhookHandler(this.extensionStorage), + new AddedToContextWebhookHandler(this.extensionStorage, this.logger), ...this.handlerChainSuffix, ) .handleWebhook.bind(this.baseHandlerChain), [instanceUpdatedKind]: this.baseHandlerChain .withAdditionalHandlers( - new InstanceUpdatedWebhookHandler(this.extensionStorage), + new InstanceUpdatedWebhookHandler(this.extensionStorage, this.logger), ...this.handlerChainSuffix, ) .handleWebhook.bind(this.baseHandlerChain), [secretRotatedKind]: this.baseHandlerChain .withAdditionalHandlers( - new SecretRotatedWebhookHandler(this.extensionStorage), + new SecretRotatedWebhookHandler(this.extensionStorage, this.logger), ...this.handlerChainSuffix, ) .handleWebhook.bind(this.baseHandlerChain), [instanceRemovedKind]: this.baseHandlerChain .withAdditionalHandlers( - new InstanceRemovedWebhookHandler(this.extensionStorage), + new InstanceRemovedWebhookHandler(this.extensionStorage, this.logger), ...this.handlerChainSuffix, ) .handleWebhook.bind(this.baseHandlerChain), diff --git a/packages/mitthooks/src/handler/addedToContext.ts b/packages/mitthooks/src/handler/addedToContext.ts index a5762c3..f0b8749 100644 --- a/packages/mitthooks/src/handler/addedToContext.ts +++ b/packages/mitthooks/src/handler/addedToContext.ts @@ -4,12 +4,15 @@ import type { WebhookContent } from "../webhook.js"; import type { ExtensionAddedToContextWebhookBody } from "../schemas.js"; import { extensionAddedToContextWebhookSchema } from "../schemas.js"; import { InvalidBodyError } from "../errors.js"; +import type {Logger} from "../logging/interface.js"; export class AddedToContextWebhookHandler implements WebhookHandler { private readonly extensionStorage: ExtensionStorage; + private readonly logger: Logger; - public constructor(extensionStorage: ExtensionStorage) { + public constructor(extensionStorage: ExtensionStorage, logger: Logger) { this.extensionStorage = extensionStorage; + this.logger = logger; } public async handleWebhook( @@ -18,12 +21,17 @@ export class AddedToContextWebhookHandler implements WebhookHandler { ): Promise { const body = this.getValidatedWebhookBody(webhookContent.rawBody); - await this.extensionStorage.upsertExtension({ - extensionInstanceId: body.id, - contextId: body.context.id, - secret: body.secret, - consentedScopes: body.consentedScopes, - }); + try { + await this.extensionStorage.upsertExtension({ + extensionInstanceId: body.id, + contextId: body.context.id, + secret: body.secret, + consentedScopes: body.consentedScopes, + }); + } catch (e) { + this.logger.error(`Failed to upsert extension: ${(e as Error).toString()}`); + throw e; + } return next(webhookContent); } diff --git a/packages/mitthooks/src/handler/combinedPersistence.ts b/packages/mitthooks/src/handler/combinedPersistence.ts index 64e9dcd..352078d 100644 --- a/packages/mitthooks/src/handler/combinedPersistence.ts +++ b/packages/mitthooks/src/handler/combinedPersistence.ts @@ -26,28 +26,34 @@ export class CombinedPersistenceWebhookHandler implements WebhookHandler { next: HandleWebhook, ): Promise { const body = this.getValidatedWebhookBody(webhookContent.rawBody); - switch (body.kind) { - case extensionAddedToContextKind: - await this.extensionStorage.upsertExtension({ - extensionInstanceId: body.id, - contextId: body.context.id, - secret: body.secret, - consentedScopes: body.consentedScopes, - }); - break; - case instanceUpdatedKind: - await this.extensionStorage.updateExtension({ - extensionInstanceId: body.id, - contextId: body.context.id, - consentedScopes: body.consentedScopes, - enabled: body.state.enabled, - }); - break; - case secretRotatedKind: - await this.extensionStorage.rotateSecret(body.id, body.secret); - break; - case instanceRemovedKind: - await this.extensionStorage.removeInstance(body.id); + + try { + switch (body.kind) { + case extensionAddedToContextKind: + await this.extensionStorage.upsertExtension({ + extensionInstanceId: body.id, + contextId: body.context.id, + secret: body.secret, + consentedScopes: body.consentedScopes, + }); + break; + case instanceUpdatedKind: + await this.extensionStorage.updateExtension({ + extensionInstanceId: body.id, + contextId: body.context.id, + consentedScopes: body.consentedScopes, + enabled: body.state.enabled, + }); + break; + case secretRotatedKind: + await this.extensionStorage.rotateSecret(body.id, body.secret); + break; + case instanceRemovedKind: + await this.extensionStorage.removeInstance(body.id); + } + } catch (e) { + this.logger.error(`Failed to persist extension: ${(e as Error).toString()}`); + throw e; } await next(webhookContent); diff --git a/packages/mitthooks/src/handler/extensionId.ts b/packages/mitthooks/src/handler/extensionId.ts index 21f8078..7927b73 100644 --- a/packages/mitthooks/src/handler/extensionId.ts +++ b/packages/mitthooks/src/handler/extensionId.ts @@ -1,22 +1,31 @@ import type {HandleWebhook, WebhookHandler} from "./interface.js"; import type {WebhookContent} from "../webhook.js"; -import {WebhookBody, webhookSchema,} from "../schemas.js"; +import type {WebhookBody} from "../schemas.js"; +import {webhookSchema} from "../schemas.js"; import {InvalidBodyError, InvalidExtensionIDError} from "../errors.js"; +import type {Logger} from "../logging/interface.js"; export class ExtensionIDVerificationWebhookHandler implements WebhookHandler { private readonly extensionID: string; + private readonly logger: Logger; - public constructor(extensionID: string) { + public constructor(extensionID: string, logger: Logger) { this.extensionID = extensionID; + this.logger = logger; } public async handleWebhook( webhookContent: WebhookContent, next: HandleWebhook, ): Promise { - const body = this.getValidatedWebhookBody(webhookContent.rawBody); - if (body.meta.extensionId !== this.extensionID) { - throw new InvalidExtensionIDError(body.meta.extensionId) + try { + const body = this.getValidatedWebhookBody(webhookContent.rawBody); + if (body.meta.extensionId !== this.extensionID) { + throw new InvalidExtensionIDError(body.meta.extensionId) + } + } catch (e) { + this.logger.error(`failed to verify extension id: ${(e as Error).toString()}`); + throw e; } return next(webhookContent); } diff --git a/packages/mitthooks/src/handler/instanceRemoved.ts b/packages/mitthooks/src/handler/instanceRemoved.ts index a021912..b398031 100644 --- a/packages/mitthooks/src/handler/instanceRemoved.ts +++ b/packages/mitthooks/src/handler/instanceRemoved.ts @@ -4,20 +4,28 @@ import type { WebhookContent } from "../webhook.js"; import type { InstanceRemovedWebhookBody } from "../schemas.js"; import { instanceRemovedWebhookSchema } from "../schemas.js"; import { InvalidBodyError } from "../errors.js"; +import type {Logger} from "../logging/interface.js"; export class InstanceRemovedWebhookHandler implements WebhookHandler { private readonly extensionStorage: ExtensionStorage; + private readonly logger: Logger; - public constructor(extensionStorage: ExtensionStorage) { + public constructor(extensionStorage: ExtensionStorage, logger: Logger) { this.extensionStorage = extensionStorage; + this.logger = logger; } public async handleWebhook( webhookContent: WebhookContent, next: HandleWebhook, ): Promise { - const body = this.getValidatedWebhookBody(webhookContent.rawBody); - await this.extensionStorage.removeInstance(body.id); + try { + const body = this.getValidatedWebhookBody(webhookContent.rawBody); + await this.extensionStorage.removeInstance(body.id); + } catch (e) { + this.logger.error(`Failed to remove instance: ${(e as Error).toString()}`); + throw e; + } return next(webhookContent); } diff --git a/packages/mitthooks/src/handler/instanceUpdated.ts b/packages/mitthooks/src/handler/instanceUpdated.ts index 490e1fc..19d7717 100644 --- a/packages/mitthooks/src/handler/instanceUpdated.ts +++ b/packages/mitthooks/src/handler/instanceUpdated.ts @@ -4,26 +4,34 @@ import type { WebhookContent } from "../webhook.js"; import type { InstanceUpdatedWebhookBody } from "../schemas.js"; import { instanceUpdatedWebhookSchema } from "../schemas.js"; import { InvalidBodyError } from "../errors.js"; +import type {Logger} from "../logging/interface.js"; export class InstanceUpdatedWebhookHandler implements WebhookHandler { private readonly extensionStorage: ExtensionStorage; + private readonly logger: Logger; - public constructor(extensionStorage: ExtensionStorage) { + public constructor(extensionStorage: ExtensionStorage, logger: Logger) { this.extensionStorage = extensionStorage; + this.logger = logger; } public async handleWebhook( webhookContent: WebhookContent, next: HandleWebhook, ): Promise { - const body = this.getValidatedWebhookBody(webhookContent.rawBody); + try { + const body = this.getValidatedWebhookBody(webhookContent.rawBody); - await this.extensionStorage.updateExtension({ - extensionInstanceId: body.id, - contextId: body.context.id, - consentedScopes: body.consentedScopes, - enabled: body.state.enabled, - }); + await this.extensionStorage.updateExtension({ + extensionInstanceId: body.id, + contextId: body.context.id, + consentedScopes: body.consentedScopes, + enabled: body.state.enabled, + }); + } catch (e) { + this.logger.error(`Failed to update extension: ${(e as Error).toString()}`); + throw e; + } return next(webhookContent); } diff --git a/packages/mitthooks/src/handler/interface.ts b/packages/mitthooks/src/handler/interface.ts index 68e3766..3f6f13f 100644 --- a/packages/mitthooks/src/handler/interface.ts +++ b/packages/mitthooks/src/handler/interface.ts @@ -1,10 +1,10 @@ import type { WebhookContent } from "../webhook.js"; -export type HandleWebhook = (webhookContent: WebhookContent) => Promise; +export type HandleWebhook = (webhookContent: WebhookContent) => Promise | void; export interface WebhookHandler { handleWebhook: ( webhookContent: WebhookContent, next: HandleWebhook, - ) => Promise; + ) => Promise | void; } diff --git a/packages/mitthooks/src/handler/secretRotated.ts b/packages/mitthooks/src/handler/secretRotated.ts index 6320e7c..815bbad 100644 --- a/packages/mitthooks/src/handler/secretRotated.ts +++ b/packages/mitthooks/src/handler/secretRotated.ts @@ -4,20 +4,28 @@ import type { WebhookContent } from "../webhook.js"; import type { SecretRotatedWebhookBody } from "../schemas.js"; import { secretRotatedWebhookSchema } from "../schemas.js"; import { InvalidBodyError } from "../errors.js"; +import type {Logger} from "../logging/interface.js"; export class SecretRotatedWebhookHandler implements WebhookHandler { private readonly extensionStorage: ExtensionStorage; + private readonly logger: Logger; - public constructor(extensionStorage: ExtensionStorage) { + public constructor(extensionStorage: ExtensionStorage, logger: Logger) { this.extensionStorage = extensionStorage; + this.logger = logger; } public async handleWebhook( webhookContent: WebhookContent, next: HandleWebhook, ): Promise { - const body = this.getValidatedWebhookBody(webhookContent.rawBody); - await this.extensionStorage.rotateSecret(body.id, body.secret); + try { + const body = this.getValidatedWebhookBody(webhookContent.rawBody); + await this.extensionStorage.rotateSecret(body.id, body.secret); + } catch (e) { + this.logger.error(`Failed to rotate secret: ${(e as Error).toString()}`); + throw e; + } return next(webhookContent); } diff --git a/packages/mitthooks/src/handler/verification.ts b/packages/mitthooks/src/handler/verification.ts index 36226b4..bd6554f 100644 --- a/packages/mitthooks/src/handler/verification.ts +++ b/packages/mitthooks/src/handler/verification.ts @@ -1,19 +1,27 @@ import type { HandleWebhook, WebhookHandler } from "./interface.js"; import type { WebhookVerifier } from "../verification/verify.js"; import type { WebhookContent } from "../webhook.js"; +import type {Logger} from "../logging/interface.js"; export class VerifyingWebhookHandler implements WebhookHandler { private readonly webhookVerifier: WebhookVerifier; + private readonly logger: Logger; - public constructor(webhookVerifier: WebhookVerifier) { + public constructor(webhookVerifier: WebhookVerifier, logger: Logger) { this.webhookVerifier = webhookVerifier; + this.logger = logger; } public async handleWebhook( webhookContent: WebhookContent, next: HandleWebhook, ): Promise { - await this.webhookVerifier.verify(webhookContent); + try { + await this.webhookVerifier.verify(webhookContent); + } catch (e) { + this.logger.error(`Failed to verify webhook signature: ${(e as Error).toString()}`); + throw e; + } return next(webhookContent); } } diff --git a/packages/mitthooks/src/verification/publicKeys.ts b/packages/mitthooks/src/verification/publicKeys.ts index eaa4951..e255d5b 100644 --- a/packages/mitthooks/src/verification/publicKeys.ts +++ b/packages/mitthooks/src/verification/publicKeys.ts @@ -12,10 +12,14 @@ export class APIPublicKeyProvider implements PublicKeyProvider { this.apiClient = apiClient; } - public static newWithUnauthenticatedAPIClient(): APIPublicKeyProvider { - return new APIPublicKeyProvider( - MittwaldAPIV2Client.newUnauthenticated(), - ); + public static newWithUnauthenticatedAPIClient(mittwaldApiUrl?: string): APIPublicKeyProvider { + const apiClient = MittwaldAPIV2Client.newUnauthenticated(); + + if (mittwaldApiUrl) { + apiClient.axios.defaults.baseURL = mittwaldApiUrl; + } + + return new APIPublicKeyProvider(apiClient); } public async getPublicKey(serial: string): Promise { diff --git a/packages/mitthooks/src/verification/verify.ts b/packages/mitthooks/src/verification/verify.ts index 241945a..0bf36ad 100644 --- a/packages/mitthooks/src/verification/verify.ts +++ b/packages/mitthooks/src/verification/verify.ts @@ -11,7 +11,7 @@ import { import { Ed25519SignatureVerificationStrategy } from "./ed25519.js"; export enum SignatureVerificationStrategyType { - ED25519 = "ED25519", + ED25519 = "ed25519", } export interface SignatureVerificationStrategy { @@ -51,12 +51,14 @@ export class WebhookVerifier { const bodyBuffer = Buffer.from(rawBody, "utf8"); const publicKeyBuffer = Buffer.from(publicKey, "base64"); - if (!(signatureAlgorithm in this.signatureVerificationStrategyMap)) { + const lowercasedSignatureAlgorithm = signatureAlgorithm.toLowerCase(); + + if (!(lowercasedSignatureAlgorithm in this.signatureVerificationStrategyMap)) { throw new UnknownSignatureAlgorithmError(signatureAlgorithm); } return this.signatureVerificationStrategyMap[ - signatureAlgorithm as SignatureVerificationStrategyType + lowercasedSignatureAlgorithm as SignatureVerificationStrategyType ].verify(signatureBuffer, bodyBuffer, publicKeyBuffer); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee60644..2465a18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,37 @@ importers: specifier: ^2.1.2 version: 2.1.2(@types/node@22.7.4) + test/nextjs: + dependencies: + '@weissaufschwarz/mitthooks': + specifier: workspace:* + version: link:../../packages/mitthooks + '@weissaufschwarz/mitthooks-nextjs': + specifier: workspace:* + version: link:../../packages/mitthooks-nextjs + next: + specifier: 15.0.3 + version: 15.0.3(react-dom@19.0.0-rc-66855b96-20241106)(react@19.0.0-rc-66855b96-20241106) + react: + specifier: 19.0.0-rc-66855b96-20241106 + version: 19.0.0-rc-66855b96-20241106 + react-dom: + specifier: 19.0.0-rc-66855b96-20241106 + version: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + devDependencies: + '@types/node': + specifier: ^20 + version: 20.17.6 + '@types/react': + specifier: ^18 + version: 18.3.12 + '@types/react-dom': + specifier: ^18 + version: 18.3.1 + typescript: + specifier: ^5 + version: 5.6.2 + packages: /@ampproject/remapping@2.3.0: @@ -467,6 +498,14 @@ packages: prettier: 2.8.8 dev: true + /@emnapi/runtime@1.3.1: + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + requiresBuild: true + dependencies: + tslib: 2.7.0 + dev: false + optional: true + /@esbuild/aix-ppc64@0.21.5: resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -733,6 +772,186 @@ packages: deprecated: Use @eslint/object-schema instead dev: true + /@img/sharp-darwin-arm64@0.33.5: + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-darwin-x64@0.33.5: + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-libvips-darwin-arm64@1.0.4: + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-darwin-x64@1.0.4: + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm64@1.0.4: + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm@1.0.5: + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-s390x@1.0.4: + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-x64@1.0.4: + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.0.4: + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.0.4: + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-linux-arm64@0.33.5: + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-arm@0.33.5: + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + dev: false + optional: true + + /@img/sharp-linux-s390x@0.33.5: + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-x64@0.33.5: + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-arm64@0.33.5: + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-x64@0.33.5: + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-wasm32@0.33.5: + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.3.1 + dev: false + optional: true + + /@img/sharp-win32-ia32@0.33.5: + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-win32-x64@0.33.5: + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@jridgewell/gen-mapping@0.3.5: resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -835,6 +1054,10 @@ packages: resolution: {integrity: sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg==} dev: false + /@next/env@15.0.3: + resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} + dev: false + /@next/swc-darwin-arm64@14.2.14: resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} engines: {node: '>= 10'} @@ -844,6 +1067,15 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64@15.0.3: + resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64@14.2.14: resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} engines: {node: '>= 10'} @@ -853,6 +1085,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64@15.0.3: + resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-gnu@14.2.14: resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} engines: {node: '>= 10'} @@ -862,6 +1103,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu@15.0.3: + resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-musl@14.2.14: resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} engines: {node: '>= 10'} @@ -871,6 +1121,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl@15.0.3: + resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu@14.2.14: resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} engines: {node: '>= 10'} @@ -880,6 +1139,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu@15.0.3: + resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-musl@14.2.14: resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} engines: {node: '>= 10'} @@ -889,6 +1157,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl@15.0.3: + resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc@14.2.14: resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} engines: {node: '>= 10'} @@ -898,6 +1175,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc@15.0.3: + resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc@14.2.14: resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} engines: {node: '>= 10'} @@ -916,6 +1202,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc@15.0.3: + resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -1097,6 +1392,12 @@ packages: resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} dev: false + /@swc/helpers@0.5.13: + resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + dependencies: + tslib: 2.7.0 + dev: false + /@swc/helpers@0.5.5: resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} dependencies: @@ -1120,6 +1421,12 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true + /@types/node@20.17.6: + resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} + dependencies: + undici-types: 6.19.8 + dev: true + /@types/node@22.7.4: resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} dependencies: @@ -1134,6 +1441,23 @@ packages: resolution: {integrity: sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==} dev: false + /@types/prop-types@15.7.13: + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + dev: true + + /@types/react-dom@18.3.1: + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + dependencies: + '@types/react': 18.3.12 + dev: true + + /@types/react@18.3.12: + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + dev: true + /@types/semver@7.5.8: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true @@ -1916,7 +2240,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -1924,7 +2247,25 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + requiresBuild: true + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + optional: true + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + requiresBuild: true + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + optional: true /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -1958,6 +2299,10 @@ packages: which: 2.0.2 dev: true + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -2078,6 +2423,13 @@ packages: engines: {node: '>=12.20'} dev: true + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + requiresBuild: true + dev: false + optional: true + /detect-newline@4.0.1: resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3170,6 +3522,12 @@ packages: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + requiresBuild: true + dev: false + optional: true + /is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -3655,6 +4013,51 @@ packages: - babel-plugin-macros dev: false + /next@15.0.3(react-dom@19.0.0-rc-66855b96-20241106)(react@19.0.0-rc-66855b96-20241106): + resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + dependencies: + '@next/env': 15.0.3 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.13 + busboy: 1.6.0 + caniuse-lite: 1.0.30001667 + postcss: 8.4.31 + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + styled-jsx: 5.1.6(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@next/swc-darwin-arm64': 15.0.3 + '@next/swc-darwin-x64': 15.0.3 + '@next/swc-linux-arm64-gnu': 15.0.3 + '@next/swc-linux-arm64-musl': 15.0.3 + '@next/swc-linux-x64-gnu': 15.0.3 + '@next/swc-linux-x64-musl': 15.0.3 + '@next/swc-win32-arm64-msvc': 15.0.3 + '@next/swc-win32-x64-msvc': 15.0.3 + sharp: 0.33.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} dev: true @@ -3985,6 +4388,15 @@ packages: scheduler: 0.23.2 dev: false + /react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106): + resolution: {integrity: sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==} + peerDependencies: + react: 19.0.0-rc-66855b96-20241106 + dependencies: + react: 19.0.0-rc-66855b96-20241106 + scheduler: 0.25.0-rc-66855b96-20241106 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true @@ -3996,6 +4408,11 @@ packages: loose-envify: 1.4.0 dev: false + /react@19.0.0-rc-66855b96-20241106: + resolution: {integrity: sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg==} + engines: {node: '>=0.10.0'} + dev: false + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -4177,6 +4594,10 @@ packages: loose-envify: 1.4.0 dev: false + /scheduler@0.25.0-rc-66855b96-20241106: + resolution: {integrity: sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA==} + dev: false + /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4191,7 +4612,6 @@ packages: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - dev: true /set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -4215,6 +4635,37 @@ packages: has-property-descriptors: 1.0.2 dev: true + /sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + dev: false + optional: true + /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -4257,6 +4708,14 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + requiresBuild: true + dependencies: + is-arrayish: 0.3.2 + dev: false + optional: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -4442,6 +4901,23 @@ packages: react: 18.3.1 dev: false + /styled-jsx@5.1.6(react@19.0.0-rc-66855b96-20241106): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + react: 19.0.0-rc-66855b96-20241106 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6de82ae..033780e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - "packages/*" + - "test/nextjs" diff --git a/test/nextjs/.gitignore b/test/nextjs/.gitignore new file mode 100644 index 0000000..d32cc78 --- /dev/null +++ b/test/nextjs/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/test/nextjs/README.md b/test/nextjs/README.md new file mode 100644 index 0000000..5d968e2 --- /dev/null +++ b/test/nextjs/README.md @@ -0,0 +1,8 @@ +# Nextjs Mitthooks Test Project + +This package is make for manual testing purposes of the mitthooks package in combination with the mitthooks-next package. +To run it locally, follow the steps below: + +1. `docker compose up -d` to start the local dev server of the mittwald marketplace +2. `pnpm run dev` to start the example nextjs project +3. `pnpm run test-webhooks` to run the test script, that executes every webhook once \ No newline at end of file diff --git a/test/nextjs/docker-compose.yaml b/test/nextjs/docker-compose.yaml new file mode 100644 index 0000000..153b3a7 --- /dev/null +++ b/test/nextjs/docker-compose.yaml @@ -0,0 +1,10 @@ +# Use this compose to run the container in its own network, exposing port 8080. + +services: + devserver: + image: mittwald/marketplace-local-dev-server:1.3.46 + ports: + - "8080:8080" + volumes: + - .env:/.env + network_mode: host diff --git a/test/nextjs/next.config.ts b/test/nextjs/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/test/nextjs/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/test/nextjs/package.json b/test/nextjs/package.json new file mode 100644 index 0000000..8fc82a9 --- /dev/null +++ b/test/nextjs/package.json @@ -0,0 +1,24 @@ +{ + "name": "@repo/nextjs", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "test-webhooks": "./scripts/test_webhooks.sh" + }, + "dependencies": { + "@weissaufschwarz/mitthooks": "workspace:*", + "@weissaufschwarz/mitthooks-nextjs": "workspace:*", + "next": "15.0.3", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } +} \ No newline at end of file diff --git a/test/nextjs/scripts/test_webhooks.sh b/test/nextjs/scripts/test_webhooks.sh new file mode 100755 index 0000000..eb188b4 --- /dev/null +++ b/test/nextjs/scripts/test_webhooks.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +curl --location 'localhost:8080/internal/calls/extensionAddedToContext' \ +--header 'Content-Type: application/json' \ +--data '{ + "context": "project", + "extensionId": "5f5b7a5e-b46b-4efc-8747-9f08e933cc8b" +}' + +curl --location 'localhost:8080/internal/calls/instanceUpdated' \ +--header 'Content-Type: application/json' \ +--data '{ + "context": "project", + "extensionId": "5f5b7a5e-b46b-4efc-8747-9f08e933cc8b" +}' + +curl --location 'localhost:8080/internal/calls/secretRotated' \ +--header 'Content-Type: application/json' \ +--data '{ + "context": "project", + "extensionId": "5f5b7a5e-b46b-4efc-8747-9f08e933cc8b" +}' + +curl --location 'localhost:8080/internal/calls/instanceRemovedFromContext' \ +--header 'Content-Type: application/json' \ +--data '{ + "context": "project", + "extensionId": "5f5b7a5e-b46b-4efc-8747-9f08e933cc8b" +}' \ No newline at end of file diff --git a/test/nextjs/src/app/api/webhooks/mittwald/route.ts b/test/nextjs/src/app/api/webhooks/mittwald/route.ts new file mode 100644 index 0000000..bf39af3 --- /dev/null +++ b/test/nextjs/src/app/api/webhooks/mittwald/route.ts @@ -0,0 +1,33 @@ +import "server-only"; +import {CombinedWebhookHandlerFactory} from "@weissaufschwarz/mitthooks/index"; +import type {ExtensionStorage, ExtensionToBeAdded, ExtensionToBeUpdated} from "@weissaufschwarz/mitthooks/storage/extensionStorage"; +import {NextJSWebhookHandler} from "@weissaufschwarz/mitthooks-nextjs/index"; + +const extensionId = "5f5b7a5e-b46b-4efc-8747-9f08e933cc8b"; + +class FakeExtensionStorage implements ExtensionStorage { + public async upsertExtension(extension: ExtensionToBeAdded): Promise { + console.log("successfully reached upsertExtension", extension); + } + + public async updateExtension(extension: ExtensionToBeUpdated):Promise { + console.log("successfully reached updateExtension", extension); + } + + public async rotateSecret(extensionInstanceId: string, secret: string):Promise { + console.log("successfully reached rotateSecret", extensionInstanceId, secret); + } + public async removeInstance(extensionInstanceId: string):Promise { + console.log("successfully reached removeInstance", extensionInstanceId); + } +} + +export async function POST(req: Request) { + const combinedHandler = new CombinedWebhookHandlerFactory(new FakeExtensionStorage(), extensionId) + .withMittwaldAPIURL("http://localhost:8080") + .build(); + const handler = new NextJSWebhookHandler(combinedHandler); + + return handler.handleWebhook(req); +} + diff --git a/test/nextjs/tsconfig.json b/test/nextjs/tsconfig.json new file mode 100644 index 0000000..c133409 --- /dev/null +++ b/test/nextjs/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}