From b9c24b6dc50551d7a13ab0bec6a4ebdadc1494e7 Mon Sep 17 00:00:00 2001 From: barsa Date: Thu, 25 Sep 2025 17:01:47 +0900 Subject: [PATCH] Refactor Freebit integration by removing the csurf dependency and updating related type definitions. Enhance Freebit API request handling with improved error management and logging. Update data structures for traffic and quota history responses to streamline data processing. Adjust service methods for better clarity and maintainability. --- apps/bff/package.json | 3 +- apps/bff/src/app/bootstrap.ts | 1 - .../freebit/interfaces/freebit.types.ts | 72 +++-- .../services/freebit-client.service.ts | 87 ++++++ .../services/freebit-mapper.service.ts | 49 ++-- .../services/freebit-operations.service.ts | 265 +++++++++++------- .../services/freebit-orchestrator.service.ts | 36 ++- .../transformers/utils/status-normalizer.ts | 7 +- .../services/sim-fulfillment.service.ts | 32 ++- .../sim-order-activation.service.ts | 26 +- apps/portal/package.json | 2 +- package.json | 3 +- .../domain/src/validation/shared/entities.ts | 1 + .../src/validation/shared/primitives.ts | 11 +- pnpm-lock.yaml | 113 +------- 15 files changed, 404 insertions(+), 304 deletions(-) diff --git a/apps/bff/package.json b/apps/bff/package.json index 817ac42b..6b946aa5 100644 --- a/apps/bff/package.json +++ b/apps/bff/package.json @@ -48,7 +48,6 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "cookie-parser": "^1.4.7", - "csurf": "^1.11.0", "express": "^4.21.2", "helmet": "^8.1.0", "ioredis": "^5.7.0", @@ -67,7 +66,7 @@ "salesforce-pubsub-api-client": "^5.5.0", "speakeasy": "^2.0.0", "uuid": "^11.1.0", - "zod": "^4.0.17" + "zod": "^4.1.9" }, "devDependencies": { "@nestjs/cli": "^11.0.10", diff --git a/apps/bff/src/app/bootstrap.ts b/apps/bff/src/app/bootstrap.ts index 17849fef..5d0e5d4a 100644 --- a/apps/bff/src/app/bootstrap.ts +++ b/apps/bff/src/app/bootstrap.ts @@ -110,7 +110,6 @@ export async function bootstrap(): Promise { "Accept", "Authorization", "X-API-Key", - "X-CSRF-Token", ], exposedHeaders: ["X-Total-Count", "X-Page-Count"], maxAge: 86400, // 24 hours diff --git a/apps/bff/src/integrations/freebit/interfaces/freebit.types.ts b/apps/bff/src/integrations/freebit/interfaces/freebit.types.ts index 410aba6d..3237ce8a 100644 --- a/apps/bff/src/integrations/freebit/interfaces/freebit.types.ts +++ b/apps/bff/src/integrations/freebit/interfaces/freebit.types.ts @@ -54,12 +54,9 @@ export interface FreebitAccountDetailsResponse { responseDatas: FreebitAccountDetail[]; } -export interface FreebitTrafficInfoResponseEntry { +export interface FreebitTrafficInfoRequest { + authKey: string; account: string; - todayUsageMb?: number | string; - todayUsageKb?: number | string; - monthlyUsageMb?: number | string; - monthlyUsageKb?: number | string; } export interface FreebitTrafficInfoResponse { @@ -68,7 +65,12 @@ export interface FreebitTrafficInfoResponse { message: string; statusCode: string | number; }; - responseDatas: FreebitTrafficInfoResponseEntry[]; + account: string; + traffic: { + today: string; // Today's usage in KB + inRecentDays: string; // Comma-separated recent days usage + blackList: string; // 10=blacklisted, 20=not blacklisted + }; } export interface FreebitTopUpRequest { @@ -104,22 +106,26 @@ export interface FreebitAddSpecResponse { status: { message: string; statusCode: string | number }; } -export interface FreebitQuotaAddition { - date?: string; - quotaMb?: number | string; - quotaKb?: number | string; - description?: string; +export interface FreebitQuotaHistoryRequest { + authKey: string; + account: string; + fromDate: string; + toDate: string; } -export interface FreebitQuotaHistoryResponseEntry { - account: string; - additions?: FreebitQuotaAddition[]; +export interface FreebitQuotaHistoryItem { + quota: string; // KB as string + date: string; + expire: string; + quotaCode: string; } export interface FreebitQuotaHistoryResponse { resultCode: string; status: { message: string; statusCode: string | number }; - responseDatas: FreebitQuotaHistoryResponseEntry[]; + total: string | number; + count: string | number; + quotaHistory: FreebitQuotaHistoryItem[]; } export interface FreebitPlanChangeRequest { @@ -226,15 +232,17 @@ export interface FreebitEsimReissueResponse { export interface FreebitEsimAddAccountRequest { authKey: string; - aladinOperated?: string; + aladinOperated: string; // '10' for issue, '20' for no-issue account: string; eid: string; addKind: "N" | "R"; // N = new, R = reissue - createType?: string; - simKind?: string; + shipDate?: string; planCode?: string; contractLine?: string; - reissue?: { oldProductNumber?: string; oldEid?: string }; + mnp?: { + reserveNumber: string; + reserveExpireDate: string; + }; } export interface FreebitEsimAddAccountResponse { @@ -272,8 +280,11 @@ export interface FreebitEsimAccountActivationRequest { } export interface FreebitEsimAccountActivationResponse { - resultCode: number | string; - status?: unknown; + resultCode: string; + status?: { + message?: string; + statusCode?: string | number; + }; statusCode?: string | number; message?: string; } @@ -303,20 +314,23 @@ export interface SimUsage { account: string; todayUsageMb: number; todayUsageKb: number; - monthlyUsageMb: number; - monthlyUsageKb: number; - recentDaysUsage?: Array<{ date: string; usageKb: number; usageMb: number }>; - lastUpdated: string; + monthlyUsageMb?: number; + monthlyUsageKb?: number; + recentDaysUsage: Array<{ date: string; usageKb: number; usageMb: number }>; + isBlacklisted: boolean; + lastUpdated?: string; } export interface SimTopUpHistory { account: string; totalAdditions: number; - additions: Array<{ - date: string; - quotaMb: number; + additionCount: number; + history: Array<{ quotaKb: number; - description: string; + quotaMb: number; + addedDate: string; + expiryDate: string; + campaignCode: string; }>; } diff --git a/apps/bff/src/integrations/freebit/services/freebit-client.service.ts b/apps/bff/src/integrations/freebit/services/freebit-client.service.ts index dd4d847b..23e406a8 100644 --- a/apps/bff/src/integrations/freebit/services/freebit-client.service.ts +++ b/apps/bff/src/integrations/freebit/services/freebit-client.service.ts @@ -109,6 +109,93 @@ export class FreebitClientService { throw new FreebitError("Request failed after all retry attempts"); } + /** + * Make an authenticated JSON request to Freebit API (for PA05-41) + */ + async makeAuthenticatedJsonRequest< + TResponse extends FreebitResponseBase, + TPayload extends object, + >(endpoint: string, payload: TPayload): Promise { + const config = this.authService.getConfig(); + const url = `${config.baseUrl}${endpoint}`; + + for (let attempt = 1; attempt <= config.retryAttempts; attempt++) { + try { + this.logger.debug(`Freebit JSON API request (attempt ${attempt}/${config.retryAttempts})`, { + url, + payload: this.sanitizePayload(payload as Record), + }); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), config.timeout); + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + + clearTimeout(timeout); + + if (!response.ok) { + throw new FreebitError( + `HTTP ${response.status}: ${response.statusText}`, + response.status.toString() + ); + } + + const responseData = (await response.json()) as TResponse; + + if (responseData.resultCode && responseData.resultCode !== "100") { + throw new FreebitError( + `API Error: ${responseData.status?.message || "Unknown error"}`, + responseData.resultCode, + responseData.status?.statusCode, + responseData.status?.message + ); + } + + this.logger.debug("Freebit JSON API request successful", { + url, + resultCode: responseData.resultCode, + }); + + return responseData; + } catch (error: unknown) { + if (error instanceof FreebitError) { + if (error.isAuthError() && attempt === 1) { + this.logger.warn("Auth error detected, clearing cache and retrying"); + this.authService.clearAuthCache(); + continue; + } + if (!error.isRetryable() || attempt === config.retryAttempts) { + throw error; + } + } + + if (attempt === config.retryAttempts) { + const message = getErrorMessage(error); + this.logger.error(`Freebit JSON API request failed after ${config.retryAttempts} attempts`, { + url, + error: message, + }); + throw new FreebitError(`Request failed: ${message}`); + } + + const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000); + this.logger.warn(`Freebit JSON API request failed, retrying in ${delay}ms`, { + url, + attempt, + error: getErrorMessage(error), + }); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw new FreebitError("Request failed after all retry attempts"); + } + /** * Make a simple request without authentication (for health checks) */ diff --git a/apps/bff/src/integrations/freebit/services/freebit-mapper.service.ts b/apps/bff/src/integrations/freebit/services/freebit-mapper.service.ts index 4eb58074..7f04eea3 100644 --- a/apps/bff/src/integrations/freebit/services/freebit-mapper.service.ts +++ b/apps/bff/src/integrations/freebit/services/freebit-mapper.service.ts @@ -70,41 +70,48 @@ export class FreebitMapperService { * Map Freebit traffic info response to SimUsage */ mapToSimUsage(response: FreebitTrafficInfoResponse): SimUsage { - const traffic = response.responseDatas[0]; - if (!traffic) { + if (!response.traffic) { throw new Error("No traffic data in response"); } + const todayUsageKb = parseInt(response.traffic.today, 10) || 0; + const recentDaysData = response.traffic.inRecentDays + .split(",") + .map((usage, index) => ({ + date: new Date(Date.now() - (index + 1) * 24 * 60 * 60 * 1000) + .toISOString() + .split("T")[0], + usageKb: parseInt(usage, 10) || 0, + usageMb: Math.round(((parseInt(usage, 10) || 0) / 1024) * 100) / 100, + })); + return { - account: String(traffic.account ?? ""), - todayUsageMb: Number(traffic.todayUsageMb ?? 0), - todayUsageKb: Number(traffic.todayUsageKb ?? 0), - monthlyUsageMb: Number(traffic.monthlyUsageMb ?? 0), - monthlyUsageKb: Number(traffic.monthlyUsageKb ?? 0), - recentDaysUsage: [], - lastUpdated: new Date().toISOString(), + account: String(response.account ?? ""), + todayUsageMb: Math.round((todayUsageKb / 1024) * 100) / 100, + todayUsageKb, + recentDaysUsage: recentDaysData, + isBlacklisted: response.traffic.blackList === "10", }; } /** * Map Freebit quota history response to SimTopUpHistory */ - mapToSimTopUpHistory(response: FreebitQuotaHistoryResponse): SimTopUpHistory { - const history = response.responseDatas[0]; - if (!history) { + mapToSimTopUpHistory(response: FreebitQuotaHistoryResponse, account: string): SimTopUpHistory { + if (!response.quotaHistory) { throw new Error("No history data in response"); } - const additions = Array.isArray(history.additions) ? history.additions : []; - return { - account: String(history.account ?? ""), - totalAdditions: additions.length, - additions: additions.map(addition => ({ - date: String(addition?.date ?? ""), - quotaMb: Number(addition?.quotaMb ?? 0), - quotaKb: Number(addition?.quotaKb ?? 0), - description: String(addition?.description ?? ""), + account, + totalAdditions: Number(response.total) || 0, + additionCount: Number(response.count) || 0, + history: response.quotaHistory.map((item) => ({ + quotaKb: parseInt(item.quota, 10), + quotaMb: Math.round((parseInt(item.quota, 10) / 1024) * 100) / 100, + addedDate: item.date, + expiryDate: item.expire, + campaignCode: item.quotaCode, })), }; } diff --git a/apps/bff/src/integrations/freebit/services/freebit-operations.service.ts b/apps/bff/src/integrations/freebit/services/freebit-operations.service.ts index 2428c03f..cd95c18d 100644 --- a/apps/bff/src/integrations/freebit/services/freebit-operations.service.ts +++ b/apps/bff/src/integrations/freebit/services/freebit-operations.service.ts @@ -15,14 +15,16 @@ import type { FreebitQuotaHistoryResponse, FreebitPlanChangeRequest, FreebitPlanChangeResponse, + FreebitAddSpecRequest, + FreebitAddSpecResponse, FreebitCancelPlanRequest, FreebitCancelPlanResponse, FreebitEsimReissueRequest, FreebitEsimReissueResponse, - FreebitAddSpecRequest, - FreebitAddSpecResponse, - FreebitPlanChangePayload, - FreebitAddSpecPayload, + FreebitEsimAddAccountRequest, + FreebitEsimAddAccountResponse, + FreebitEsimAccountActivationRequest, + FreebitEsimAccountActivationResponse, SimDetails, SimUsage, SimTopUpHistory, @@ -114,9 +116,7 @@ export class FreebitOperationsService { */ async getSimUsage(account: string): Promise { try { - const request: Omit = { - requestDatas: [{ kind: "MVNO", account }], - } as any; + const request: Omit = { account }; const response = await this.client.makeAuthenticatedRequest< FreebitTrafficInfoResponse, @@ -140,26 +140,35 @@ export class FreebitOperationsService { async topUpSim( account: string, quotaMb: number, - options: { description?: string } = {} + options: { campaignCode?: string; expiryDate?: string; scheduledAt?: string } = {} ): Promise { try { + const quotaKb = Math.round(quotaMb * 1024); const request: Omit = { - requestDatas: [ - { - kind: "MVNO", - account, - quotaMb, - description: options.description || `Data top-up: ${quotaMb}MB`, - }, - ], - } as any; + account, + quota: quotaKb, + quotaCode: options.campaignCode, + expire: options.expiryDate, + }; + + const scheduled = !!options.scheduledAt; + const endpoint = scheduled ? "/mvno/eachQuota/" : "/master/addSpec/"; + if (scheduled) { + (request as any).runTime = options.scheduledAt; + } await this.client.makeAuthenticatedRequest( - "/mvno/addQuota/", + endpoint, request ); - this.logger.log(`Successfully topped up ${quotaMb}MB for account ${account}`); + this.logger.log(`Successfully topped up ${quotaMb}MB for account ${account}`, { + account, + endpoint, + quotaMb, + quotaKb, + scheduled, + }); } catch (error) { const message = getErrorMessage(error); this.logger.error(`Failed to top up SIM for account ${account}`, { @@ -180,23 +189,18 @@ export class FreebitOperationsService { toDate: string ): Promise { try { - const request: Omit = { - requestDatas: [ - { - kind: "MVNO", - account, - fromDate, - toDate, - }, - ], - } as any; + const request: Omit = { + account, + fromDate, + toDate + }; const response = await this.client.makeAuthenticatedRequest< FreebitQuotaHistoryResponse, typeof request >("/mvno/getQuotaHistory/", request); - return this.mapper.mapToSimTopUpHistory(response); + return this.mapper.mapToSimTopUpHistory(response, account); } catch (error) { const message = getErrorMessage(error); this.logger.error(`Failed to get SIM top-up history for account ${account}`, { @@ -218,30 +222,28 @@ export class FreebitOperationsService { options: { assignGlobalIp?: boolean; scheduledAt?: string } = {} ): Promise<{ ipv4?: string; ipv6?: string }> { try { - const request: FreebitPlanChangePayload = { - requestDatas: [ - { - kind: "MVNO", - account, - newPlanCode, - assignGlobalIp: options.assignGlobalIp ?? false, - scheduledAt: options.scheduledAt, - }, - ], + const request: Omit = { + account, + plancode: newPlanCode, + globalip: options.assignGlobalIp ? "1" : "0", + runTime: options.scheduledAt, }; const response = await this.client.makeAuthenticatedRequest< FreebitPlanChangeResponse, - FreebitPlanChangePayload + typeof request >("/mvno/changePlan/", request); - const result = response.responseDatas?.[0] ?? {}; - - this.logger.log(`Successfully changed plan for account ${account} to ${newPlanCode}`); + this.logger.log(`Successfully changed plan for account ${account} to ${newPlanCode}`, { + account, + newPlanCode, + assignGlobalIp: options.assignGlobalIp, + scheduled: !!options.scheduledAt, + }); return { - ipv4: result.ipv4, - ipv6: result.ipv6, + ipv4: response.ipv4, + ipv6: response.ipv6, }; } catch (error) { const message = getErrorMessage(error); @@ -267,51 +269,37 @@ export class FreebitOperationsService { } ): Promise { try { - const requests: FreebitAddSpecPayload[] = []; + const request: Omit = { account }; - const createSpecPayload = ( - specCode: string, - additional: Partial = {} - ): FreebitAddSpecPayload => ({ - requestDatas: [ - { - kind: "MVNO", - account, - specCode, - ...additional, - }, - ], - }); - - // Voice options (PA05-06) + // Use both variations for compatibility if (typeof features.voiceMailEnabled === "boolean") { - requests.push(createSpecPayload("PA05-06", { enabled: features.voiceMailEnabled })); + request.voiceMail = features.voiceMailEnabled ? "10" : "20"; + request.voicemail = request.voiceMail; } - if (typeof features.callWaitingEnabled === "boolean") { - requests.push(createSpecPayload("PA05-06", { enabled: features.callWaitingEnabled })); + request.callWaiting = features.callWaitingEnabled ? "10" : "20"; + request.callwaiting = request.callWaiting; } - if (typeof features.internationalRoamingEnabled === "boolean") { - requests.push( - createSpecPayload("PA05-06", { enabled: features.internationalRoamingEnabled }) - ); + request.worldWing = features.internationalRoamingEnabled ? "10" : "20"; + request.worldwing = request.worldWing; } - - // Network type (PA05-38 for contract line change) if (features.networkType) { - requests.push(createSpecPayload("PA05-38", { networkType: features.networkType })); + request.contractLine = features.networkType; } - // Execute all requests - for (const request of requests) { - await this.client.makeAuthenticatedRequest( - "/mvno/addSpec/", - request - ); - } + await this.client.makeAuthenticatedRequest( + "/master/addSpec/", + request + ); - this.logger.log(`Successfully updated SIM features for account ${account}`, { features }); + this.logger.log(`Successfully updated SIM features for account ${account}`, { + account, + voiceMailEnabled: features.voiceMailEnabled, + callWaitingEnabled: features.callWaitingEnabled, + internationalRoamingEnabled: features.internationalRoamingEnabled, + networkType: features.networkType, + }); } catch (error) { const message = getErrorMessage(error); this.logger.error(`Failed to update SIM features for account ${account}`, { @@ -328,22 +316,20 @@ export class FreebitOperationsService { */ async cancelSim(account: string, scheduledAt?: string): Promise { try { - const request: FreebitCancelPlanPayload = { - requestDatas: [ - { - kind: "MVNO", - account, - runDate: scheduledAt || this.mapper.formatDateForApi(new Date()), - }, - ], + const request: Omit = { + account, + runTime: scheduledAt }; - await this.client.makeAuthenticatedRequest( - "/mvno/cancelPlan/", + await this.client.makeAuthenticatedRequest( + "/mvno/releasePlan/", request ); - this.logger.log(`Successfully cancelled SIM for account ${account}`); + this.logger.log(`Successfully cancelled SIM for account ${account}`, { + account, + runTime: scheduledAt + }); } catch (error) { const message = getErrorMessage(error); this.logger.error(`Failed to cancel SIM for account ${account}`, { @@ -386,33 +372,31 @@ export class FreebitOperationsService { async reissueEsimProfileEnhanced( account: string, newEid: string, - options: { oldEid?: string; planCode?: string } = {} + options: { oldProductNumber?: string; oldEid?: string; planCode?: string } = {} ): Promise { try { - const request: FreebitEsimReissuePayload = { - requestDatas: [ - { - kind: "MVNO", - account, - newEid, - oldEid: options.oldEid, - planCode: options.planCode, - }, - ], + const request: Omit = { + aladinOperated: "20", + account, + eid: newEid, + addKind: "R", + planCode: options.planCode, }; - await this.client.makeAuthenticatedRequest( - "/mvno/reissueEsim/", + await this.client.makeAuthenticatedRequest( + "/mvno/esim/addAcnt/", request ); - this.logger.log(`Successfully reissued eSIM profile with new EID for account ${account}`, { + this.logger.log(`Successfully reissued eSIM profile via addAcnt for account ${account}`, { + account, newEid, + oldProductNumber: options.oldProductNumber, oldEid: options.oldEid, }); } catch (error) { const message = getErrorMessage(error); - this.logger.error(`Failed to reissue eSIM profile with new EID for account ${account}`, { + this.logger.error(`Failed to reissue eSIM profile via addAcnt for account ${account}`, { account, newEid, error: message, @@ -421,6 +405,75 @@ export class FreebitOperationsService { } } + /** + * Activate new eSIM account using PA05-41 (addAcct) + */ + async activateEsimAccountNew(params: { + account: string; + eid: string; + planCode?: string; + contractLine?: "4G" | "5G"; + aladinOperated?: "10" | "20"; + shipDate?: string; + mnp?: { reserveNumber: string; reserveExpireDate: string }; + identity?: { + firstnameKanji?: string; + lastnameKanji?: string; + firstnameZenKana?: string; + lastnameZenKana?: string; + gender?: string; + birthday?: string; + }; + }): Promise { + const { account, eid, planCode, contractLine, aladinOperated = "10", shipDate, mnp, identity } = params; + + if (!account || !eid) { + throw new BadRequestException("activateEsimAccountNew requires account and eid"); + } + + try { + const payload: FreebitEsimAccountActivationRequest = { + authKey: await this.auth.getAuthKey(), + aladinOperated, + createType: "new", + eid, + account, + simkind: "esim", + planCode, + contractLine, + shipDate, + ...(mnp ? { mnp } : {}), + ...(identity ? identity : {}), + } as FreebitEsimAccountActivationRequest; + + // Use JSON request for PA05-41 + await this.client.makeAuthenticatedJsonRequest< + FreebitEsimAccountActivationResponse, + FreebitEsimAccountActivationRequest + >( + "/mvno/esim/addAcct/", + payload + ); + + this.logger.log("Successfully activated new eSIM account via PA05-41", { + account, + planCode, + contractLine, + scheduled: !!shipDate, + mnp: !!mnp, + }); + } catch (error) { + const message = getErrorMessage(error); + this.logger.error(`Failed to activate new eSIM account ${account}`, { + account, + eid, + planCode, + error: message, + }); + throw new BadRequestException(`Failed to activate new eSIM account: ${message}`); + } + } + /** * Health check - test API connectivity */ diff --git a/apps/bff/src/integrations/freebit/services/freebit-orchestrator.service.ts b/apps/bff/src/integrations/freebit/services/freebit-orchestrator.service.ts index f834a43f..0b4fcdf9 100644 --- a/apps/bff/src/integrations/freebit/services/freebit-orchestrator.service.ts +++ b/apps/bff/src/integrations/freebit/services/freebit-orchestrator.service.ts @@ -5,6 +5,7 @@ import type { SimDetails, SimUsage, SimTopUpHistory, + FreebitEsimAddAccountRequest, } from "../interfaces/freebit.types"; @Injectable() @@ -36,7 +37,7 @@ export class FreebitOrchestratorService { async topUpSim( account: string, quotaMb: number, - options: { description?: string } = {} + options: { campaignCode?: string; expiryDate?: string; scheduledAt?: string } = {} ): Promise { const normalizedAccount = this.mapper.normalizeAccount(account); return this.operations.topUpSim(normalizedAccount, quotaMb, options); @@ -110,6 +111,39 @@ export class FreebitOrchestratorService { return this.operations.reissueEsimProfileEnhanced(normalizedAccount, newEid, options); } + /** + * Activate new eSIM account + */ + async activateEsimAccountNew(params: { + account: string; + eid: string; + planCode?: string; + contractLine?: "4G" | "5G"; + aladinOperated?: "10" | "20"; + shipDate?: string; + mnp?: { reserveNumber: string; reserveExpireDate: string }; + identity?: { + firstnameKanji?: string; + lastnameKanji?: string; + firstnameZenKana?: string; + lastnameZenKana?: string; + gender?: string; + birthday?: string; + }; + }): Promise { + const normalizedAccount = this.mapper.normalizeAccount(params.account); + return this.operations.activateEsimAccountNew({ + account: normalizedAccount, + eid: params.eid, + planCode: params.planCode, + contractLine: params.contractLine, + aladinOperated: params.aladinOperated, + shipDate: params.shipDate, + mnp: params.mnp, + identity: params.identity, + }); + } + /** * Health check */ diff --git a/apps/bff/src/integrations/whmcs/transformers/utils/status-normalizer.ts b/apps/bff/src/integrations/whmcs/transformers/utils/status-normalizer.ts index 510e0c85..73401f18 100644 --- a/apps/bff/src/integrations/whmcs/transformers/utils/status-normalizer.ts +++ b/apps/bff/src/integrations/whmcs/transformers/utils/status-normalizer.ts @@ -50,11 +50,12 @@ export class StatusNormalizer { monthly: "Monthly", quarterly: "Quarterly", semiannually: "Semi-Annually", + "semi-annually": "Semi-Annually", annually: "Annually", biennially: "Biennially", triennially: "Triennially", - onetime: "One Time", - "one time": "One Time", + onetime: "One-time", + "one time": "One-time", free: "Free", }; @@ -65,7 +66,7 @@ export class StatusNormalizer { * Check if billing cycle represents a one-time payment */ static isOneTimeBilling(cycle: string): boolean { - const oneTimeCycles = ["onetime", "one time", "free"]; + const oneTimeCycles = ["onetime", "one time", "one-time", "free"]; return oneTimeCycles.includes(cycle?.toLowerCase()); } diff --git a/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts b/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts index 667c689f..88647ccc 100644 --- a/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts +++ b/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts @@ -54,15 +54,29 @@ export class SimFulfillmentService { throw new Error("Phone number is required for SIM activation"); } - await this.activateSim({ - account: phoneNumber, - eid, - planSku, - simType, - activationType, - scheduledAt, - mnp, - }); + if (simType === "eSIM") { + if (!eid) { + throw new Error("EID is required for eSIM activation"); + } + await this.activateSim({ + account: phoneNumber, + eid, + planSku, + simType: "eSIM", + activationType, + scheduledAt, + mnp, + }); + } else { + await this.activateSim({ + account: phoneNumber, + planSku, + simType: "Physical SIM", + activationType, + scheduledAt, + mnp, + }); + } this.logger.log("SIM fulfillment completed successfully", { orderId: orderDetails.id, diff --git a/apps/bff/src/modules/subscriptions/sim-order-activation.service.ts b/apps/bff/src/modules/subscriptions/sim-order-activation.service.ts index 4ec279d6..24ba9498 100644 --- a/apps/bff/src/modules/subscriptions/sim-order-activation.service.ts +++ b/apps/bff/src/modules/subscriptions/sim-order-activation.service.ts @@ -87,33 +87,15 @@ export class SimOrderActivationService { await this.freebit.activateEsimAccountNew({ account: req.msisdn, eid: req.eid!, - planSku: req.planSku, + planCode: req.planSku, contractLine: "5G", shipDate: req.activationType === "Scheduled" ? req.scheduledAt : undefined, mnp: req.mnp ? { - reservationNumber: req.mnp.reserveNumber, - expiryDate: req.mnp.reserveExpireDate, - phoneNumber: req.mnp.account || "", - mvnoAccountNumber: "", - portingLastName: "", - portingFirstName: "", - portingLastNameKatakana: "", - portingFirstNameKatakana: "", - portingGender: "" as const, - portingDateOfBirth: "" + reserveNumber: req.mnp.reserveNumber || "", + reserveExpireDate: req.mnp.reserveExpireDate || "" } - : undefined, - identity: req.mnp - ? { - firstnameKanji: req.mnp.firstnameKanji, - lastnameKanji: req.mnp.lastnameKanji, - firstnameZenKana: req.mnp.firstnameZenKana, - lastnameZenKana: req.mnp.lastnameZenKana, - gender: req.mnp.gender, - birthday: req.mnp.birthday, - } - : undefined, + : undefined }); } else { this.logger.warn("Physical SIM activation path is not implemented; skipping Freebit call", { diff --git a/apps/portal/package.json b/apps/portal/package.json index 55472d5b..26f640df 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -33,7 +33,7 @@ "tailwind-merge": "^3.3.1", "tw-animate-css": "^1.3.7", "world-countries": "^5.1.0", - "zod": "^4.0.17", + "zod": "^4.1.9", "zustand": "^5.0.8" }, "devDependencies": { diff --git a/package.json b/package.json index a98980f9..e4d5289f 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "zod": "^4.1.9" }, "dependencies": { - "@sendgrid/mail": "^8.1.5", - "csurf": "^1.11.0" + "@sendgrid/mail": "^8.1.5" } } diff --git a/packages/domain/src/validation/shared/entities.ts b/packages/domain/src/validation/shared/entities.ts index bcbca52e..fc2c4fe3 100644 --- a/packages/domain/src/validation/shared/entities.ts +++ b/packages/domain/src/validation/shared/entities.ts @@ -71,6 +71,7 @@ const subscriptionCycleSchema = z.enum([ "Biennially", "Triennially", "One-time", + "Free", ]); // ===================================================== diff --git a/packages/domain/src/validation/shared/primitives.ts b/packages/domain/src/validation/shared/primitives.ts index 0dfe3c69..f2de834e 100644 --- a/packages/domain/src/validation/shared/primitives.ts +++ b/packages/domain/src/validation/shared/primitives.ts @@ -98,7 +98,16 @@ export const categoryEnum = z.enum(["technical", "billing", "account", "general" // Billing cycle enums export const billingCycleEnum = z.enum(["Monthly", "Quarterly", "Annually", "Onetime", "Free"]); -export const subscriptionBillingCycleEnum = z.enum(["Monthly", "Quarterly", "Annually", "Biennially", "Triennially"]); +export const subscriptionBillingCycleEnum = z.enum([ + "Monthly", + "Quarterly", + "Semi-Annually", + "Annually", + "Biennially", + "Triennially", + "One-time", + "Free" +]); // ===================================================== // TYPE EXPORTS diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f869e99..91287763 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@sendgrid/mail': specifier: ^8.1.5 version: 8.1.5 - csurf: - specifier: ^1.11.0 - version: 1.11.0 devDependencies: '@eslint/eslintrc': specifier: ^3.3.1 @@ -114,9 +111,6 @@ importers: cookie-parser: specifier: ^1.4.7 version: 1.4.7 - csurf: - specifier: ^1.11.0 - version: 1.11.0 express: specifier: ^4.21.2 version: 4.21.2 @@ -137,7 +131,7 @@ importers: version: 4.4.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@10.5.0)(pino@9.9.5)(rxjs@7.8.2) nestjs-zod: specifier: ^5.0.1 - version: 5.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.1.5) + version: 5.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.1.9) passport: specifier: ^0.7.0 version: 0.7.0 @@ -172,8 +166,8 @@ importers: specifier: ^11.1.0 version: 11.1.0 zod: - specifier: ^4.0.17 - version: 4.1.5 + specifier: ^4.1.9 + version: 4.1.9 devDependencies: '@nestjs/cli': specifier: ^11.0.10 @@ -302,8 +296,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 zod: - specifier: ^4.0.17 - version: 4.1.5 + specifier: ^4.1.9 + version: 4.1.9 zustand: specifier: ^5.0.8 version: 5.0.8(@types/react@19.1.12)(react@19.1.1) @@ -2540,10 +2534,6 @@ packages: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.4.0: - resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==} - engines: {node: '>= 0.6'} - cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -2592,18 +2582,9 @@ packages: resolution: {integrity: sha512-D3WAbvvgUVIqSxUfdvLeGjuotsB32bvfVPd+AaaTWMtyUeC9zgCnw5xs94no89yFLVsafvY9dMZEhTwsY/ZecA==} engines: {node: '>=0.6.0'} - csrf@3.1.0: - resolution: {integrity: sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==} - engines: {node: '>= 0.8'} - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - csurf@1.11.0: - resolution: {integrity: sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==} - engines: {node: '>= 0.8.0'} - deprecated: This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions - csv-parse@5.6.0: resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} @@ -2697,10 +2678,6 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} - depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -3325,10 +3302,6 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - http-errors@1.7.3: - resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} - engines: {node: '>= 0.6'} - http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -4519,10 +4492,6 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - random-bytes@1.0.0: - resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} - engines: {node: '>= 0.8'} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -4637,9 +4606,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rndm@1.2.0: - resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==} - router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -4743,9 +4709,6 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - setprototypeof@1.1.1: - resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -4833,10 +4796,6 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} - statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -5033,10 +4992,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toidentifier@1.0.0: - resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} - engines: {node: '>=0.6'} - toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -5117,10 +5072,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - tsx@4.20.5: resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} engines: {node: '>=18.0.0'} @@ -5197,10 +5148,6 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - uid-safe@2.1.5: - resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} - engines: {node: '>= 0.8'} - uid@2.0.2: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} @@ -5408,9 +5355,6 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} - zod@4.1.5: - resolution: {integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==} - zod@4.1.9: resolution: {integrity: sha512-HI32jTq0AUAC125z30E8bQNz0RQ+9Uc+4J7V97gLYjZVKRjeydPgGt6dvQzFrav7MYOUGFqqOGiHpA/fdbd0cQ==} @@ -7717,8 +7661,6 @@ snapshots: cookie-signature@1.2.2: {} - cookie@0.4.0: {} - cookie@0.7.1: {} cookie@0.7.2: {} @@ -7761,21 +7703,8 @@ snapshots: dependencies: sequin: 0.1.1 - csrf@3.1.0: - dependencies: - rndm: 1.2.0 - tsscmp: 1.0.6 - uid-safe: 2.1.5 - csstype@3.1.3: {} - csurf@1.11.0: - dependencies: - cookie: 0.4.0 - cookie-signature: 1.0.6 - csrf: 3.1.0 - http-errors: 1.7.3 - csv-parse@5.6.0: {} csv-stringify@6.6.0: {} @@ -7846,8 +7775,6 @@ snapshots: denque@2.1.0: {} - depd@1.1.2: {} - depd@2.0.0: {} destr@2.0.5: {} @@ -8711,14 +8638,6 @@ snapshots: html-escaper@2.0.2: {} - http-errors@1.7.3: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.1.1 - statuses: 1.5.0 - toidentifier: 1.0.0 - http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -9695,12 +9614,12 @@ snapshots: pino-http: 10.5.0 rxjs: 7.8.2 - nestjs-zod@5.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.1.5): + nestjs-zod@5.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.1.9): dependencies: '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) deepmerge: 4.3.1 rxjs: 7.8.2 - zod: 4.1.5 + zod: 4.1.9 optionalDependencies: '@nestjs/swagger': 11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) @@ -10097,8 +10016,6 @@ snapshots: quick-format-unescaped@4.0.4: {} - random-bytes@1.0.0: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -10212,8 +10129,6 @@ snapshots: reusify@1.1.0: {} - rndm@1.2.0: {} - router@2.2.0: dependencies: debug: 4.4.1 @@ -10378,8 +10293,6 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - setprototypeof@1.1.1: {} - setprototypeof@1.2.0: {} sharp@0.34.3: @@ -10491,8 +10404,6 @@ snapshots: standard-as-callback@2.1.0: {} - statuses@1.5.0: {} - statuses@2.0.1: {} statuses@2.0.2: {} @@ -10705,8 +10616,6 @@ snapshots: dependencies: is-number: 7.0.0 - toidentifier@1.0.0: {} - toidentifier@1.0.1: {} token-types@6.1.1: @@ -10787,8 +10696,6 @@ snapshots: tslib@2.8.1: {} - tsscmp@1.0.6: {} - tsx@4.20.5: dependencies: esbuild: 0.25.10 @@ -10876,10 +10783,6 @@ snapshots: uglify-js@3.19.3: optional: true - uid-safe@2.1.5: - dependencies: - random-bytes: 1.0.0 - uid@2.0.2: dependencies: '@lukeed/csprng': 1.1.0 @@ -11130,8 +11033,6 @@ snapshots: yoctocolors-cjs@2.1.3: {} - zod@4.1.5: {} - zod@4.1.9: {} zustand@5.0.8(@types/react@19.1.12)(react@19.1.1):