alepha 0.20.5 → 0.20.7
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/AGENTS.md +0 -1
- package/CLAUDE.md +0 -1
- package/assets/agents-template.md +0 -1
- package/dist/api/audits/index.browser.js +1 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +701 -654
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +24 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +1 -0
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +193 -166
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +52 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +40 -14
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +639 -333
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +495 -162
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +222 -188
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +54 -0
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +265 -236
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +55 -13
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/organizations/index.d.ts +100 -97
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +332 -314
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +37 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +431 -376
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +202 -87
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +1695 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1919 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +1001 -844
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +237 -28
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +123 -122
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +21 -2
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +47 -0
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +24 -0
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +134 -7
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +181 -15
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +181 -15
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +156 -0
- package/dist/cache/database/index.d.ts.map +1 -0
- package/dist/cache/database/index.js +266 -0
- package/dist/cache/database/index.js.map +1 -0
- package/dist/cache/redis/index.d.ts +3 -2
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +142 -128
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +160 -13
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -2
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +346 -290
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +106 -7
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +12 -11
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.d.ts +6 -5
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +119 -118
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.browser.js.map +1 -1
- package/dist/crypto/index.d.ts +3 -2
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/core/index.d.ts +3 -2
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.d.ts +7 -6
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +5 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +10 -9
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +9 -8
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +9 -3
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +31 -10
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +33 -14
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +31 -10
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +6 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +5 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/core/index.workerd.js.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -2
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +5 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +8 -4
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +2 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +206 -205
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +11 -11
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +25 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +12 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +12 -0
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js +29 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +82 -35
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +56 -3
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +163 -158
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -4
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +35 -34
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +7 -6
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.js.map +1 -1
- package/dist/server/health/index.d.ts +16 -15
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +51 -50
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +6 -5
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.d.ts +3 -2
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/topic/redis/index.js.map +1 -1
- package/package.json +33 -39
- package/src/api/audits/controllers/AdminAuditController.ts +29 -0
- package/src/api/audits/entities/audits.ts +1 -0
- package/src/api/files/controllers/FileController.ts +24 -0
- package/src/api/files/entities/files.ts +1 -0
- package/src/api/files/services/FileService.ts +41 -0
- package/src/api/jobs/__tests__/$job.spec.ts +501 -24
- package/src/api/jobs/entities/jobExecutionEntity.ts +4 -3
- package/src/api/jobs/index.ts +47 -10
- package/src/api/jobs/primitives/$job.ts +22 -9
- package/src/api/jobs/providers/DirectJobDispatcher.ts +71 -0
- package/src/api/jobs/providers/JobDispatcher.ts +49 -0
- package/src/api/jobs/providers/JobProvider.ts +385 -147
- package/src/api/jobs/providers/JobQueueProvider.ts +43 -18
- package/src/api/jobs/schemas/jobConfigAtom.ts +9 -3
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +11 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +4 -2
- package/src/api/jobs/services/JobService.ts +21 -11
- package/src/api/keys/controllers/AdminApiKeyController.ts +23 -0
- package/src/api/keys/entities/apiKeyEntity.ts +1 -0
- package/src/api/keys/services/ApiKeyService.ts +42 -0
- package/src/api/notifications/__tests__/AlephaApiNotifications.spec.ts +63 -0
- package/src/api/notifications/controllers/AdminNotificationController.ts +48 -1
- package/src/api/notifications/index.ts +13 -3
- package/src/api/notifications/jobs/NotificationJobs.ts +0 -6
- package/src/api/parameters/controllers/AdminParameterController.ts +26 -0
- package/src/api/parameters/services/ParameterProvider.ts +18 -0
- package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
- package/src/api/payments/index.ts +3 -0
- package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
- package/src/api/payments/providers/PaymentProvider.ts +25 -9
- package/src/api/payments/services/PaymentService.ts +3 -0
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +133 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/Registration-emailMode.spec.ts +203 -0
- package/src/api/users/__tests__/UsernameSlugger.spec.ts +138 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +41 -3
- package/src/api/users/controllers/AdminSessionController.ts +29 -0
- package/src/api/users/controllers/AdminUserController.ts +32 -0
- package/src/api/users/index.ts +3 -0
- package/src/api/users/services/CredentialService.ts +5 -0
- package/src/api/users/services/RegistrationService.ts +49 -1
- package/src/api/users/services/SessionCrudService.ts +16 -0
- package/src/api/users/services/SessionService.ts +17 -59
- package/src/api/users/services/UsernameSlugger.ts +195 -0
- package/src/bucket/primitives/$bucket.ts +21 -0
- package/src/bucket/providers/CloudflareR2Provider.ts +15 -0
- package/src/bucket/providers/FileStorageProvider.ts +9 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +14 -0
- package/src/bucket/providers/MemoryFileStorageProvider.ts +9 -0
- package/src/bucket/providers/NodeS3BucketProvider.ts +35 -0
- package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
- package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
- package/src/cache/core/index.ts +16 -0
- package/src/cache/core/primitives/$cache.ts +367 -24
- package/src/cache/database/__tests__/DatabaseCacheProvider.behavior.spec.ts +203 -0
- package/src/cache/database/__tests__/DatabaseCacheProvider.spec.ts +110 -0
- package/src/cache/database/entities/cacheEntries.ts +55 -0
- package/src/cache/database/index.ts +36 -0
- package/src/cache/database/providers/DatabaseCacheProvider.ts +348 -0
- package/src/cli/core/services/ProjectScaffolder.ts +0 -2
- package/src/cli/core/tasks/BuildCloudflareTask.ts +33 -3
- package/src/cli/core/tasks/BuildSitemapTask.ts +7 -0
- package/src/cli/core/tasks/BuildVercelTask.ts +82 -3
- package/src/cli/core/templates/agentMd.ts +39 -4
- package/src/cli/core/templates/biomeJson.ts +25 -1
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
- package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
- package/src/cli/platform/__tests__/detectResources.spec.ts +96 -0
- package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
- package/src/cli/platform/atoms/platformOptions.ts +13 -0
- package/src/cli/platform/commands/platform.ts +7 -1
- package/src/cli/platform/schemas/platform.ts +1 -0
- package/src/cli/platform/services/CloudflareApi.ts +61 -0
- package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
- package/src/core/__tests__/$module.spec.ts +2 -2
- package/src/core/primitives/$module.ts +4 -4
- package/src/mcp/providers/McpServerProvider.ts +1 -1
- package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/core/schemas/insertSchema.ts +10 -2
- package/src/orm/core/services/Repository.ts +27 -7
- package/src/react/form/hooks/useFormState.ts +8 -1
- package/src/react/form/index.ts +10 -1
- package/src/react/form/services/FormModel.ts +9 -3
- package/src/scheduler/index.ts +14 -0
- package/src/scheduler/providers/CronProvider.ts +13 -0
- package/src/security/atoms/currentTenantAtom.ts +34 -0
- package/src/security/index.browser.ts +1 -0
- package/src/security/index.ts +12 -1
- package/src/security/primitives/$issuer.ts +17 -1
- package/src/security/providers/SecurityProvider.ts +37 -0
- package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
- package/tsconfig.base.json +2 -1
- package/dist/react/websocket/index.d.ts +0 -117
- package/dist/react/websocket/index.d.ts.map +0 -1
- package/dist/react/websocket/index.js +0 -108
- package/dist/react/websocket/index.js.map +0 -1
- package/dist/websocket/index.browser.js +0 -844
- package/dist/websocket/index.browser.js.map +0 -1
- package/dist/websocket/index.d.ts +0 -876
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/index.js +0 -1175
- package/dist/websocket/index.js.map +0 -1
- package/src/react/websocket/hooks/useRoom.tsx +0 -251
- package/src/react/websocket/index.ts +0 -7
- package/src/websocket/__tests__/$channel.spec.ts +0 -30
- package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
- package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
- package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
- package/src/websocket/errors/WebSocketError.ts +0 -34
- package/src/websocket/index.browser.ts +0 -25
- package/src/websocket/index.shared.ts +0 -8
- package/src/websocket/index.ts +0 -85
- package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
- package/src/websocket/primitives/$channel.ts +0 -131
- package/src/websocket/primitives/$websocket.ts +0 -107
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
- package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
- package/src/websocket/services/RoomManager.ts +0 -160
- package/src/websocket/services/WebSocketClient.ts +0 -642
- package/src/websocket/services/WebSocketTopicService.ts +0 -108
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage } from "node:http";
|
|
2
|
-
import {
|
|
3
|
-
$atom,
|
|
4
|
-
$hook,
|
|
5
|
-
$inject,
|
|
6
|
-
$state,
|
|
7
|
-
Alepha,
|
|
8
|
-
AlephaError,
|
|
9
|
-
SchemaValidator,
|
|
10
|
-
type Static,
|
|
11
|
-
t,
|
|
12
|
-
} from "alepha";
|
|
13
|
-
import { $logger } from "alepha/logger";
|
|
14
|
-
import { WebSocket, WebSocketServer } from "ws";
|
|
15
|
-
import { WebSocketValidationError } from "../errors/WebSocketError.ts";
|
|
16
|
-
import type {
|
|
17
|
-
EmitOptions,
|
|
18
|
-
WebSocketConnection,
|
|
19
|
-
WebSocketHandlerContext,
|
|
20
|
-
WebSocketPrimitiveOptions,
|
|
21
|
-
WebSocketState,
|
|
22
|
-
} from "../interfaces/WebSocketInterfaces.ts";
|
|
23
|
-
import type { TWSObject } from "../primitives/$channel.ts";
|
|
24
|
-
import { RoomManager } from "../services/RoomManager.ts";
|
|
25
|
-
import { WebSocketTopicService } from "../services/WebSocketTopicService.ts";
|
|
26
|
-
import { WebSocketServerProvider } from "./WebSocketServerProvider.ts";
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* WebSocket configuration atom.
|
|
32
|
-
*/
|
|
33
|
-
export const websocketOptions = $atom({
|
|
34
|
-
name: "alepha.websocket.options",
|
|
35
|
-
schema: t.object({
|
|
36
|
-
path: t.text({
|
|
37
|
-
default: "/ws",
|
|
38
|
-
description: "Base path for WebSocket endpoints.",
|
|
39
|
-
}),
|
|
40
|
-
}),
|
|
41
|
-
default: {
|
|
42
|
-
path: "/ws",
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
export type WebSocketOptions = Static<typeof websocketOptions.schema>;
|
|
47
|
-
|
|
48
|
-
declare module "alepha" {
|
|
49
|
-
interface State {
|
|
50
|
-
[websocketOptions.key]: WebSocketOptions;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
export class NodeWebSocketServerProvider extends WebSocketServerProvider {
|
|
57
|
-
protected readonly alepha = $inject(Alepha);
|
|
58
|
-
protected readonly roomManager = $inject(RoomManager);
|
|
59
|
-
protected readonly topicService = $inject(WebSocketTopicService);
|
|
60
|
-
protected readonly log = $logger();
|
|
61
|
-
protected readonly wsOptions = $state(websocketOptions);
|
|
62
|
-
|
|
63
|
-
protected wss?: WebSocketServer;
|
|
64
|
-
protected endpoints = new Map<string, WebSocketPrimitiveOptions<any, any>>();
|
|
65
|
-
protected connections = new Map<string, WebSocketConnection>();
|
|
66
|
-
protected userConnections = new Map<string, Set<string>>(); // userId → Set<connectionId>
|
|
67
|
-
protected nextConnectionId = 1;
|
|
68
|
-
|
|
69
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
70
|
-
|
|
71
|
-
public registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(
|
|
72
|
-
config: WebSocketPrimitiveOptions<TClient, TServer>,
|
|
73
|
-
): void {
|
|
74
|
-
const path = config.channel.options.path;
|
|
75
|
-
this.endpoints.set(path, config);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public async emit<TClient extends TWSObject>(
|
|
79
|
-
channelPath: string,
|
|
80
|
-
options: EmitOptions<TClient>,
|
|
81
|
-
): Promise<void> {
|
|
82
|
-
// Publish to topic so all server instances receive it
|
|
83
|
-
await this.topicService.publish({
|
|
84
|
-
channelPath,
|
|
85
|
-
roomIds: options.roomIds
|
|
86
|
-
? options.roomIds
|
|
87
|
-
: options.roomId
|
|
88
|
-
? [options.roomId]
|
|
89
|
-
: undefined,
|
|
90
|
-
userIds: options.userIds
|
|
91
|
-
? options.userIds
|
|
92
|
-
: options.userId
|
|
93
|
-
? [options.userId]
|
|
94
|
-
: undefined,
|
|
95
|
-
connectionIds: options.connectionIds
|
|
96
|
-
? options.connectionIds
|
|
97
|
-
: options.connectionId
|
|
98
|
-
? [options.connectionId]
|
|
99
|
-
: undefined,
|
|
100
|
-
exceptConnectionIds: options.exceptConnectionIds,
|
|
101
|
-
exceptUserIds: options.exceptUserIds,
|
|
102
|
-
message: options.message,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
public getConnections(): WebSocketConnection[] {
|
|
107
|
-
return Array.from(this.connections.values());
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public getRoomConnections(roomId: string): WebSocketConnection[] {
|
|
111
|
-
const connectionIds = this.roomManager.getRoomConnections(roomId);
|
|
112
|
-
return connectionIds
|
|
113
|
-
.map((id) => this.connections.get(id))
|
|
114
|
-
.filter((conn): conn is WebSocketConnection => conn !== undefined);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
public getUserConnections(userId: string): WebSocketConnection[] {
|
|
118
|
-
const connectionIds = this.userConnections.get(userId);
|
|
119
|
-
if (!connectionIds) {
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
return Array.from(connectionIds)
|
|
123
|
-
.map((id) => this.connections.get(id))
|
|
124
|
-
.filter((conn): conn is WebSocketConnection => conn !== undefined);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
public async closeConnection(
|
|
128
|
-
connectionId: string,
|
|
129
|
-
code?: number,
|
|
130
|
-
reason?: string,
|
|
131
|
-
): Promise<void> {
|
|
132
|
-
const connection = this.connections.get(connectionId);
|
|
133
|
-
if (!connection) {
|
|
134
|
-
this.log.warn(`Connection not found: ${connectionId}`);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
await connection.close(code, reason);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
protected handleUpgrade(
|
|
143
|
-
request: IncomingMessage,
|
|
144
|
-
socket: any,
|
|
145
|
-
head: Buffer,
|
|
146
|
-
): boolean {
|
|
147
|
-
const url = new URL(request.url || "/", "http://localhost");
|
|
148
|
-
const path = url.pathname;
|
|
149
|
-
|
|
150
|
-
const endpoint = this.endpoints.get(path);
|
|
151
|
-
if (!endpoint) {
|
|
152
|
-
// Not our endpoint - in Vite dev mode, let Vite HMR handle it
|
|
153
|
-
// In production, destroy the socket
|
|
154
|
-
if (!this.alepha.isViteDev()) {
|
|
155
|
-
this.log.warn(`No WebSocket endpoint found for path: ${path}`);
|
|
156
|
-
socket.destroy();
|
|
157
|
-
}
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
this.log.debug(`WebSocket upgrade request: ${path}`);
|
|
162
|
-
|
|
163
|
-
this.wss?.handleUpgrade(request, socket, head, (ws) => {
|
|
164
|
-
this.handleConnection(ws, endpoint, request);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
protected handleConnection<
|
|
171
|
-
TClient extends TWSObject,
|
|
172
|
-
TServer extends TWSObject,
|
|
173
|
-
>(
|
|
174
|
-
ws: WebSocket,
|
|
175
|
-
endpoint: WebSocketPrimitiveOptions<TClient, TServer>,
|
|
176
|
-
request: IncomingMessage,
|
|
177
|
-
): void {
|
|
178
|
-
const connectionId = `ws-${this.nextConnectionId++}`;
|
|
179
|
-
|
|
180
|
-
// TODO: Extract userId from the WebSocket upgrade request.
|
|
181
|
-
// Parse JWT from cookies or Authorization header during handshake.
|
|
182
|
-
// Until implemented, maxConnectionsPerUser has no effect.
|
|
183
|
-
const userId: string | undefined = undefined;
|
|
184
|
-
|
|
185
|
-
// Extract roomIds from query params (e.g., ?roomId=room1&roomId=room2 or ?roomIds=room1,room2)
|
|
186
|
-
const url = new URL(request.url || "/", "http://localhost");
|
|
187
|
-
const roomIds = this.extractRoomIds(url);
|
|
188
|
-
|
|
189
|
-
// Check max connections per user before registering
|
|
190
|
-
if (userId && endpoint.maxConnectionsPerUser) {
|
|
191
|
-
const existingConns = this.userConnections.get(userId);
|
|
192
|
-
if (
|
|
193
|
-
existingConns &&
|
|
194
|
-
existingConns.size >= endpoint.maxConnectionsPerUser
|
|
195
|
-
) {
|
|
196
|
-
this.log.warn(
|
|
197
|
-
`User ${userId} exceeded max connections (${endpoint.maxConnectionsPerUser})`,
|
|
198
|
-
);
|
|
199
|
-
ws.close(1008, "Max connections per user exceeded");
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const connection = this.alepha.inject(NodeWebSocketConnection, {
|
|
205
|
-
lifetime: "transient",
|
|
206
|
-
args: [connectionId, userId, roomIds, ws, this, endpoint],
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
this.connections.set(connectionId, connection);
|
|
210
|
-
|
|
211
|
-
// Track user connections
|
|
212
|
-
if (userId) {
|
|
213
|
-
let userConns = this.userConnections.get(userId);
|
|
214
|
-
if (!userConns) {
|
|
215
|
-
userConns = new Set();
|
|
216
|
-
this.userConnections.set(userId, userConns);
|
|
217
|
-
}
|
|
218
|
-
userConns.add(connectionId);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Join rooms
|
|
222
|
-
if (roomIds.length > 0) {
|
|
223
|
-
this.roomManager.joinRooms(connectionId, roomIds);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
this.log.info(`WebSocket connection established: ${connectionId}`, {
|
|
227
|
-
path: endpoint.channel.options.path,
|
|
228
|
-
userId,
|
|
229
|
-
roomIds,
|
|
230
|
-
remoteAddress: request.socket.remoteAddress,
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Call onConnect handler if provided
|
|
234
|
-
if (endpoint.onConnect) {
|
|
235
|
-
Promise.resolve(
|
|
236
|
-
endpoint.onConnect({ connectionId, userId, roomIds }),
|
|
237
|
-
).catch((error) => {
|
|
238
|
-
this.log.error("Error in onConnect handler:", error);
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
ws.on("message", (data) => {
|
|
243
|
-
connection.handleMessage(data).catch((error) => {
|
|
244
|
-
this.log.error(
|
|
245
|
-
`Unhandled error in message handler for ${connectionId}:`,
|
|
246
|
-
error,
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
ws.on("close", (code, reason) => {
|
|
252
|
-
this.log.info(`WebSocket connection closed: ${connectionId}`, {
|
|
253
|
-
code,
|
|
254
|
-
reason: reason.toString(),
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Clean up
|
|
258
|
-
this.connections.delete(connectionId);
|
|
259
|
-
this.roomManager.leaveAllRooms(connectionId);
|
|
260
|
-
|
|
261
|
-
if (userId) {
|
|
262
|
-
const userConns = this.userConnections.get(userId);
|
|
263
|
-
if (userConns) {
|
|
264
|
-
userConns.delete(connectionId);
|
|
265
|
-
if (userConns.size === 0) {
|
|
266
|
-
this.userConnections.delete(userId);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Call onDisconnect handler if provided
|
|
272
|
-
if (endpoint.onDisconnect) {
|
|
273
|
-
Promise.resolve(
|
|
274
|
-
endpoint.onDisconnect({ connectionId, userId, roomIds }),
|
|
275
|
-
).catch((error) => {
|
|
276
|
-
this.log.error("Error in onDisconnect handler:", error);
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
ws.on("error", (error) => {
|
|
282
|
-
this.log.error(`WebSocket error on ${connectionId}:`, error);
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
protected extractRoomIds(url: URL): string[] {
|
|
287
|
-
const roomIds: string[] = [];
|
|
288
|
-
|
|
289
|
-
// Check for roomId parameter (can be multiple)
|
|
290
|
-
const roomIdParams = url.searchParams.getAll("roomId");
|
|
291
|
-
roomIds.push(...roomIdParams);
|
|
292
|
-
|
|
293
|
-
// Check for roomIds parameter (comma-separated)
|
|
294
|
-
const roomIdsParam = url.searchParams.get("roomIds");
|
|
295
|
-
if (roomIdsParam) {
|
|
296
|
-
roomIds.push(
|
|
297
|
-
...roomIdsParam
|
|
298
|
-
.split(",")
|
|
299
|
-
.map((id) => id.trim())
|
|
300
|
-
.filter((id) => id.length > 0),
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Default room if none specified
|
|
305
|
-
if (roomIds.length === 0) {
|
|
306
|
-
roomIds.push("default");
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return roomIds;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Send message to local connections based on targeting criteria
|
|
314
|
-
* This is called by the topic service when a message is received
|
|
315
|
-
*/
|
|
316
|
-
protected async sendToLocalConnections(
|
|
317
|
-
channelPath: string,
|
|
318
|
-
message: any,
|
|
319
|
-
criteria: {
|
|
320
|
-
roomIds?: string[];
|
|
321
|
-
userIds?: string[];
|
|
322
|
-
connectionIds?: string[];
|
|
323
|
-
exceptConnectionIds?: string[];
|
|
324
|
-
exceptUserIds?: string[];
|
|
325
|
-
},
|
|
326
|
-
): Promise<void> {
|
|
327
|
-
const targetConnections = new Set<string>();
|
|
328
|
-
|
|
329
|
-
// Helper to check if a connection belongs to this channel
|
|
330
|
-
const isOnChannel = (connId: string) => {
|
|
331
|
-
const conn = this.connections.get(connId);
|
|
332
|
-
return conn?.channelPath === channelPath;
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
// Collect target connections based on criteria
|
|
336
|
-
if (criteria.roomIds) {
|
|
337
|
-
for (const roomId of criteria.roomIds) {
|
|
338
|
-
const roomConns = this.roomManager.getRoomConnections(roomId);
|
|
339
|
-
for (const connId of roomConns) {
|
|
340
|
-
if (isOnChannel(connId)) {
|
|
341
|
-
targetConnections.add(connId);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (criteria.userIds) {
|
|
348
|
-
for (const userId of criteria.userIds) {
|
|
349
|
-
const userConns = this.userConnections.get(userId);
|
|
350
|
-
if (userConns) {
|
|
351
|
-
for (const connId of userConns) {
|
|
352
|
-
if (isOnChannel(connId)) {
|
|
353
|
-
targetConnections.add(connId);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (criteria.connectionIds) {
|
|
361
|
-
for (const connId of criteria.connectionIds) {
|
|
362
|
-
if (isOnChannel(connId)) {
|
|
363
|
-
targetConnections.add(connId);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// If no specific targeting, send to all connections on this channel
|
|
369
|
-
if (!criteria.roomIds && !criteria.userIds && !criteria.connectionIds) {
|
|
370
|
-
for (const conn of this.connections.values()) {
|
|
371
|
-
if (conn.channelPath === channelPath) {
|
|
372
|
-
targetConnections.add(conn.id);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Remove exceptions
|
|
378
|
-
if (criteria.exceptConnectionIds) {
|
|
379
|
-
for (const connId of criteria.exceptConnectionIds) {
|
|
380
|
-
targetConnections.delete(connId);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (criteria.exceptUserIds) {
|
|
385
|
-
for (const userId of criteria.exceptUserIds) {
|
|
386
|
-
const userConns = this.userConnections.get(userId);
|
|
387
|
-
if (userConns) {
|
|
388
|
-
for (const connId of userConns) {
|
|
389
|
-
targetConnections.delete(connId);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Send to all target connections
|
|
396
|
-
const serialized = JSON.stringify(message);
|
|
397
|
-
await Promise.all(
|
|
398
|
-
Array.from(targetConnections).map(async (connId) => {
|
|
399
|
-
const conn = this.connections.get(connId);
|
|
400
|
-
if (conn) {
|
|
401
|
-
try {
|
|
402
|
-
await conn.send(serialized);
|
|
403
|
-
} catch (error) {
|
|
404
|
-
this.log.error(`Failed to send to connection ${connId}:`, error);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}),
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
412
|
-
|
|
413
|
-
protected readonly start = $hook({
|
|
414
|
-
on: "start",
|
|
415
|
-
handler: async () => {
|
|
416
|
-
if (this.alepha.isServerless()) {
|
|
417
|
-
this.log.debug("WebSocket server disabled in serverless mode");
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
this.wss = new WebSocketServer({ noServer: true });
|
|
422
|
-
|
|
423
|
-
for (const [path, endpoint] of this.endpoints.entries()) {
|
|
424
|
-
this.log.debug(`WebSocket endpoint registered: ${path}`);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Set up topic service message handler
|
|
428
|
-
this.topicService.setMessageHandler(async (event) => {
|
|
429
|
-
await this.sendToLocalConnections(event.channelPath, event.message, {
|
|
430
|
-
roomIds: event.roomIds,
|
|
431
|
-
userIds: event.userIds,
|
|
432
|
-
connectionIds: event.connectionIds,
|
|
433
|
-
exceptConnectionIds: event.exceptConnectionIds,
|
|
434
|
-
exceptUserIds: event.exceptUserIds,
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
this.log.info("WebSocket server OK", {
|
|
439
|
-
basePath: this.wsOptions.path,
|
|
440
|
-
});
|
|
441
|
-
},
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
protected readonly ready = $hook({
|
|
445
|
-
on: "ready",
|
|
446
|
-
handler: async () => {
|
|
447
|
-
if (this.alepha.isServerless() || !this.wss) {
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Attach upgrade handler to the HTTP server (must be done after HTTP server starts)
|
|
452
|
-
const httpServer = this.alepha.store.get("alepha.node.server");
|
|
453
|
-
if (httpServer) {
|
|
454
|
-
httpServer.on("upgrade", (request, socket, head) => {
|
|
455
|
-
this.handleUpgrade(request, socket, head);
|
|
456
|
-
});
|
|
457
|
-
this.log.debug("WebSocket upgrade handler attached to HTTP server");
|
|
458
|
-
} else {
|
|
459
|
-
this.log.warn(
|
|
460
|
-
"No HTTP server found - WebSocket upgrade handler not attached",
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
},
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
protected readonly stop = $hook({
|
|
467
|
-
on: "stop",
|
|
468
|
-
handler: async () => {
|
|
469
|
-
if (!this.wss) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Close all connections (collect into array to avoid mutation during iteration)
|
|
474
|
-
const connections = Array.from(this.connections.values());
|
|
475
|
-
for (const connection of connections) {
|
|
476
|
-
await connection.close(1001, "Server shutting down");
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
await new Promise<void>((resolve, reject) => {
|
|
480
|
-
this.wss?.close((err) => {
|
|
481
|
-
if (err) {
|
|
482
|
-
reject(err);
|
|
483
|
-
} else {
|
|
484
|
-
resolve();
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
this.log.info("WebSocket server closed");
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
495
|
-
|
|
496
|
-
export class NodeWebSocketConnection implements WebSocketConnection {
|
|
497
|
-
protected readonly log = $logger();
|
|
498
|
-
protected readonly schemaValidator = $inject(SchemaValidator);
|
|
499
|
-
public metadata?: Record<string, any>;
|
|
500
|
-
|
|
501
|
-
constructor(
|
|
502
|
-
public readonly id: string,
|
|
503
|
-
public readonly userId: string | undefined,
|
|
504
|
-
public readonly roomIds: string[],
|
|
505
|
-
protected readonly ws: WebSocket,
|
|
506
|
-
protected readonly provider: NodeWebSocketServerProvider,
|
|
507
|
-
protected readonly endpoint: WebSocketPrimitiveOptions<any, any>,
|
|
508
|
-
) {}
|
|
509
|
-
|
|
510
|
-
public get channelPath(): string {
|
|
511
|
-
return this.endpoint.channel.options.path;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
public get readyState(): WebSocketState {
|
|
515
|
-
return this.ws.readyState;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
public async send(message: any): Promise<void> {
|
|
519
|
-
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
520
|
-
throw new AlephaError("WebSocket is not open");
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const data =
|
|
524
|
-
typeof message === "string" ? message : JSON.stringify(message);
|
|
525
|
-
await new Promise<void>((resolve, reject) => {
|
|
526
|
-
this.ws.send(data, (err) => {
|
|
527
|
-
if (err) {
|
|
528
|
-
reject(err);
|
|
529
|
-
} else {
|
|
530
|
-
resolve();
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
public async close(code?: number, reason?: string): Promise<void> {
|
|
537
|
-
this.ws.close(code, reason);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
public async handleMessage(data: any): Promise<void> {
|
|
541
|
-
try {
|
|
542
|
-
const rawMessage = data.toString();
|
|
543
|
-
let parsed: any;
|
|
544
|
-
|
|
545
|
-
try {
|
|
546
|
-
parsed = JSON.parse(rawMessage);
|
|
547
|
-
} catch {
|
|
548
|
-
this.log.warn("Received non-JSON message");
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Extract roomId from message (or use first room in connection's rooms)
|
|
553
|
-
const roomId = parsed.roomId || this.roomIds[0] || "default";
|
|
554
|
-
|
|
555
|
-
// Extract message payload
|
|
556
|
-
const message = parsed.message || parsed;
|
|
557
|
-
|
|
558
|
-
// Validate message against schema (out = client→server)
|
|
559
|
-
const outSchema = this.endpoint.channel.options.schema.out;
|
|
560
|
-
try {
|
|
561
|
-
this.schemaValidator.validate(outSchema, message, {
|
|
562
|
-
trim: false,
|
|
563
|
-
nullToUndefined: false,
|
|
564
|
-
deleteUndefined: false,
|
|
565
|
-
});
|
|
566
|
-
} catch (err) {
|
|
567
|
-
throw new WebSocketValidationError(
|
|
568
|
-
`Message validation failed: ${(err as Error).message}`,
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Create reply function scoped to this context
|
|
573
|
-
const reply = async (options: {
|
|
574
|
-
message: any;
|
|
575
|
-
roomId?: string;
|
|
576
|
-
exceptSelf?: boolean;
|
|
577
|
-
exceptConnectionIds?: string[];
|
|
578
|
-
exceptUserIds?: string[];
|
|
579
|
-
}) => {
|
|
580
|
-
const targetRoomId = options.roomId || roomId;
|
|
581
|
-
const exceptConnectionIds = options.exceptConnectionIds || [];
|
|
582
|
-
|
|
583
|
-
if (options.exceptSelf) {
|
|
584
|
-
exceptConnectionIds.push(this.id);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
await this.provider.emit(this.endpoint.channel.options.path, {
|
|
588
|
-
message: options.message,
|
|
589
|
-
roomId: targetRoomId,
|
|
590
|
-
exceptConnectionIds,
|
|
591
|
-
exceptUserIds: options.exceptUserIds,
|
|
592
|
-
});
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
const context: WebSocketHandlerContext<any, any> = {
|
|
596
|
-
connectionId: this.id,
|
|
597
|
-
userId: this.userId,
|
|
598
|
-
roomId,
|
|
599
|
-
message,
|
|
600
|
-
reply,
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
await this.endpoint.handler(context);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
this.log.error(`Error handling WebSocket message on ${this.id}:`, error);
|
|
606
|
-
|
|
607
|
-
// Send error back to client (best-effort, may not match channel schema)
|
|
608
|
-
try {
|
|
609
|
-
await this.send({
|
|
610
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
611
|
-
});
|
|
612
|
-
} catch {
|
|
613
|
-
// Connection may already be closed
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EmitOptions,
|
|
3
|
-
WebSocketConnection,
|
|
4
|
-
WebSocketPrimitiveOptions,
|
|
5
|
-
} from "../interfaces/WebSocketInterfaces.ts";
|
|
6
|
-
import type { TWSObject } from "../primitives/$channel.ts";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Abstract WebSocket server provider
|
|
10
|
-
*
|
|
11
|
-
* This class provides the base interface that must be implemented by
|
|
12
|
-
* platform-specific providers (Node.js, Browser, etc.)
|
|
13
|
-
*/
|
|
14
|
-
export abstract class WebSocketServerProvider {
|
|
15
|
-
/**
|
|
16
|
-
* Register a WebSocket endpoint with its channel configuration
|
|
17
|
-
*/
|
|
18
|
-
abstract registerEndpoint<
|
|
19
|
-
TClient extends TWSObject,
|
|
20
|
-
TServer extends TWSObject,
|
|
21
|
-
>(config: WebSocketPrimitiveOptions<TClient, TServer>): void;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Emit a message to clients based on targeting criteria
|
|
25
|
-
*
|
|
26
|
-
* This method distributes messages across all server instances via pub/sub.
|
|
27
|
-
*/
|
|
28
|
-
abstract emit<TClient extends TWSObject>(
|
|
29
|
-
channelPath: string,
|
|
30
|
-
options: EmitOptions<TClient>,
|
|
31
|
-
): Promise<void>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get all active connections (local to this server instance)
|
|
35
|
-
*/
|
|
36
|
-
abstract getConnections(): WebSocketConnection[];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get connections in a specific room (local to this server instance)
|
|
40
|
-
*/
|
|
41
|
-
abstract getRoomConnections(roomId: string): WebSocketConnection[];
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get connections for a specific user (local to this server instance)
|
|
45
|
-
*/
|
|
46
|
-
abstract getUserConnections(userId: string): WebSocketConnection[];
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Close a specific connection
|
|
50
|
-
*/
|
|
51
|
-
abstract closeConnection(
|
|
52
|
-
connectionId: string,
|
|
53
|
-
code?: number,
|
|
54
|
-
reason?: string,
|
|
55
|
-
): Promise<void>;
|
|
56
|
-
}
|