alepha 0.20.6 → 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 +370 -355
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +1 -0
- 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 +179 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +1 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +7 -0
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +271 -262
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +21 -3
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +198 -192
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +1 -0
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +246 -245
- package/dist/api/notifications/index.d.ts.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/parameters/index.d.ts +323 -320
- package/dist/api/parameters/index.d.ts.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 +863 -847
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/verifications/index.d.ts +126 -125
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/bucket/index.d.ts +3 -2
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts +114 -4
- 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 +20 -19
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts +3 -2
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cli/core/index.d.ts +113 -129
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +75 -7
- 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/platform/index.d.ts +346 -290
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +105 -6
- 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/command/index.d.ts +5 -4
- package/dist/command/index.d.ts.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.d.ts +3 -2
- package/dist/crypto/index.d.ts.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/smtp/index.d.ts +7 -6
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/lock/core/index.d.ts +5 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +10 -9
- package/dist/logger/index.d.ts.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.d.ts +6 -5
- package/dist/orm/postgres/index.d.ts.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/redis/index.d.ts +3 -2
- package/dist/queue/redis/index.d.ts.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 +6 -4
- package/dist/react/form/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/router/index.d.ts +206 -205
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/ui/index.d.ts +11 -11
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +3 -2
- package/dist/scheduler/index.d.ts.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/core/index.d.ts +35 -34
- package/dist/server/core/index.d.ts.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/health/index.d.ts +16 -15
- package/dist/server/health/index.d.ts.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/rate-limit/index.d.ts +6 -5
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts +3 -2
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/package.json +16 -32
- package/src/api/audits/entities/audits.ts +1 -0
- package/src/api/files/entities/files.ts +1 -0
- package/src/api/jobs/__tests__/$job.spec.ts +92 -40
- package/src/api/jobs/entities/jobExecutionEntity.ts +1 -0
- package/src/api/jobs/providers/JobProvider.ts +20 -5
- package/src/api/jobs/schemas/jobConfigAtom.ts +5 -0
- package/src/api/keys/entities/apiKeyEntity.ts +1 -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/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 +347 -21
- package/src/cli/core/tasks/BuildCloudflareTask.ts +16 -0
- 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/adapters/CloudflareAdapter.ts +104 -7
- package/src/cli/platform/atoms/platformOptions.ts +13 -0
- 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/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 -848
- 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 -1185
- 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,951 +0,0 @@
|
|
|
1
|
-
import { Alepha, t } from "alepha";
|
|
2
|
-
import { NodeHttpServerProvider } from "alepha/server";
|
|
3
|
-
import { describe, test } from "vitest";
|
|
4
|
-
import WebSocket from "ws";
|
|
5
|
-
import { AlephaWebSocket } from "../index.ts";
|
|
6
|
-
import { $channel } from "../primitives/$channel.ts";
|
|
7
|
-
import { $websocket } from "../primitives/$websocket.ts";
|
|
8
|
-
import { NodeWebSocketServerProvider } from "../providers/NodeWebSocketServerProvider.ts";
|
|
9
|
-
import { RoomManager } from "../services/RoomManager.ts";
|
|
10
|
-
|
|
11
|
-
// Helpers
|
|
12
|
-
|
|
13
|
-
function waitForOpen(ws: WebSocket) {
|
|
14
|
-
return new Promise<void>((resolve) => ws.on("open", resolve));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function collectMessages(ws: WebSocket, messages: any[] = []) {
|
|
18
|
-
ws.on("message", (data) => messages.push(JSON.parse(data.toString())));
|
|
19
|
-
return messages;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function waitForMessage(ws: WebSocket): Promise<any> {
|
|
23
|
-
return new Promise((resolve) =>
|
|
24
|
-
ws.once("message", (data) => resolve(JSON.parse(data.toString()))),
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function waitForClose(
|
|
29
|
-
ws: WebSocket,
|
|
30
|
-
): Promise<{ code: number; reason: string }> {
|
|
31
|
-
return new Promise((resolve) =>
|
|
32
|
-
ws.on("close", (code, reason) =>
|
|
33
|
-
resolve({ code, reason: reason.toString() }),
|
|
34
|
-
),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
-
|
|
40
|
-
// Schemas used across tests
|
|
41
|
-
const chatInSchema = t.object({
|
|
42
|
-
type: t.text(),
|
|
43
|
-
content: t.text(),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const chatOutSchema = t.object({
|
|
47
|
-
content: t.text(),
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe("WebSocket integration", () => {
|
|
51
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
52
|
-
// Connection lifecycle
|
|
53
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
describe("connection lifecycle", () => {
|
|
56
|
-
test("onConnect and onDisconnect callbacks", async ({ expect }) => {
|
|
57
|
-
const events: string[] = [];
|
|
58
|
-
|
|
59
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
60
|
-
|
|
61
|
-
class Controller {
|
|
62
|
-
ch = $channel({
|
|
63
|
-
path: "/ws/lifecycle",
|
|
64
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
ws = $websocket({
|
|
68
|
-
channel: this.ch,
|
|
69
|
-
handler: async () => {},
|
|
70
|
-
onConnect: ({ connectionId, roomIds }) => {
|
|
71
|
-
events.push(`connect:${connectionId}:${roomIds.join(",")}`);
|
|
72
|
-
},
|
|
73
|
-
onDisconnect: ({ connectionId }) => {
|
|
74
|
-
events.push(`disconnect:${connectionId}`);
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
alepha.inject(Controller);
|
|
80
|
-
await alepha.start();
|
|
81
|
-
|
|
82
|
-
const hostname = alepha
|
|
83
|
-
.inject(NodeHttpServerProvider)
|
|
84
|
-
.hostname.replace("http://", "ws://");
|
|
85
|
-
|
|
86
|
-
const ws = new WebSocket(`${hostname}/ws/lifecycle?roomId=lobby`);
|
|
87
|
-
await waitForOpen(ws);
|
|
88
|
-
await delay(100);
|
|
89
|
-
|
|
90
|
-
expect(events.length).toBe(1);
|
|
91
|
-
expect(events[0]).toMatch(/^connect:ws-\d+:lobby$/);
|
|
92
|
-
|
|
93
|
-
ws.close();
|
|
94
|
-
await delay(100);
|
|
95
|
-
|
|
96
|
-
expect(events.length).toBe(2);
|
|
97
|
-
expect(events[1]).toMatch(/^disconnect:ws-\d+$/);
|
|
98
|
-
|
|
99
|
-
await alepha.stop();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("default room assignment when no roomId provided", async ({
|
|
103
|
-
expect,
|
|
104
|
-
}) => {
|
|
105
|
-
const roomIds: string[] = [];
|
|
106
|
-
|
|
107
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
108
|
-
|
|
109
|
-
class Controller {
|
|
110
|
-
ch = $channel({
|
|
111
|
-
path: "/ws/default-room",
|
|
112
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
ws = $websocket({
|
|
116
|
-
channel: this.ch,
|
|
117
|
-
handler: async () => {},
|
|
118
|
-
onConnect: ({ roomIds: ids }) => {
|
|
119
|
-
roomIds.push(...ids);
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
alepha.inject(Controller);
|
|
125
|
-
await alepha.start();
|
|
126
|
-
|
|
127
|
-
const hostname = alepha
|
|
128
|
-
.inject(NodeHttpServerProvider)
|
|
129
|
-
.hostname.replace("http://", "ws://");
|
|
130
|
-
|
|
131
|
-
const ws = new WebSocket(`${hostname}/ws/default-room`);
|
|
132
|
-
await waitForOpen(ws);
|
|
133
|
-
await delay(100);
|
|
134
|
-
|
|
135
|
-
expect(roomIds).toEqual(["default"]);
|
|
136
|
-
|
|
137
|
-
ws.close();
|
|
138
|
-
await alepha.stop();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("multiple roomIds via comma-separated query param", async ({
|
|
142
|
-
expect,
|
|
143
|
-
}) => {
|
|
144
|
-
const roomIds: string[] = [];
|
|
145
|
-
|
|
146
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
147
|
-
|
|
148
|
-
class Controller {
|
|
149
|
-
ch = $channel({
|
|
150
|
-
path: "/ws/multi-room",
|
|
151
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
ws = $websocket({
|
|
155
|
-
channel: this.ch,
|
|
156
|
-
handler: async () => {},
|
|
157
|
-
onConnect: ({ roomIds: ids }) => {
|
|
158
|
-
roomIds.push(...ids);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
alepha.inject(Controller);
|
|
164
|
-
await alepha.start();
|
|
165
|
-
|
|
166
|
-
const hostname = alepha
|
|
167
|
-
.inject(NodeHttpServerProvider)
|
|
168
|
-
.hostname.replace("http://", "ws://");
|
|
169
|
-
|
|
170
|
-
const ws = new WebSocket(`${hostname}/ws/multi-room?roomIds=a,b,c`);
|
|
171
|
-
await waitForOpen(ws);
|
|
172
|
-
await delay(100);
|
|
173
|
-
|
|
174
|
-
expect(roomIds).toEqual(["a", "b", "c"]);
|
|
175
|
-
|
|
176
|
-
ws.close();
|
|
177
|
-
await alepha.stop();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("connection cleanup removes from room manager", async ({ expect }) => {
|
|
181
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
182
|
-
|
|
183
|
-
class Controller {
|
|
184
|
-
ch = $channel({
|
|
185
|
-
path: "/ws/cleanup",
|
|
186
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
ws = $websocket({
|
|
190
|
-
channel: this.ch,
|
|
191
|
-
handler: async () => {},
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
alepha.inject(Controller);
|
|
196
|
-
await alepha.start();
|
|
197
|
-
|
|
198
|
-
const hostname = alepha
|
|
199
|
-
.inject(NodeHttpServerProvider)
|
|
200
|
-
.hostname.replace("http://", "ws://");
|
|
201
|
-
const roomManager = alepha.inject(RoomManager);
|
|
202
|
-
|
|
203
|
-
const ws = new WebSocket(`${hostname}/ws/cleanup?roomId=test-room`);
|
|
204
|
-
await waitForOpen(ws);
|
|
205
|
-
await delay(100);
|
|
206
|
-
|
|
207
|
-
expect(roomManager.getRoomConnections("test-room")).toHaveLength(1);
|
|
208
|
-
|
|
209
|
-
ws.close();
|
|
210
|
-
await delay(100);
|
|
211
|
-
|
|
212
|
-
expect(roomManager.getRoomConnections("test-room")).toHaveLength(0);
|
|
213
|
-
|
|
214
|
-
await alepha.stop();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
219
|
-
// Message handling
|
|
220
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
221
|
-
|
|
222
|
-
describe("message handling", () => {
|
|
223
|
-
test("handler receives correct context", async ({ expect }) => {
|
|
224
|
-
let receivedContext: any = null;
|
|
225
|
-
|
|
226
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
227
|
-
|
|
228
|
-
class Controller {
|
|
229
|
-
ch = $channel({
|
|
230
|
-
path: "/ws/context",
|
|
231
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
ws = $websocket({
|
|
235
|
-
channel: this.ch,
|
|
236
|
-
handler: async (ctx) => {
|
|
237
|
-
receivedContext = {
|
|
238
|
-
connectionId: ctx.connectionId,
|
|
239
|
-
roomId: ctx.roomId,
|
|
240
|
-
message: ctx.message,
|
|
241
|
-
hasReply: typeof ctx.reply === "function",
|
|
242
|
-
};
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
alepha.inject(Controller);
|
|
248
|
-
await alepha.start();
|
|
249
|
-
|
|
250
|
-
const hostname = alepha
|
|
251
|
-
.inject(NodeHttpServerProvider)
|
|
252
|
-
.hostname.replace("http://", "ws://");
|
|
253
|
-
|
|
254
|
-
const ws = new WebSocket(`${hostname}/ws/context?roomId=lobby`);
|
|
255
|
-
await waitForOpen(ws);
|
|
256
|
-
await delay(50);
|
|
257
|
-
|
|
258
|
-
ws.send(
|
|
259
|
-
JSON.stringify({ roomId: "lobby", message: { content: "hello" } }),
|
|
260
|
-
);
|
|
261
|
-
await delay(200);
|
|
262
|
-
|
|
263
|
-
expect(receivedContext).not.toBeNull();
|
|
264
|
-
expect(receivedContext.connectionId).toMatch(/^ws-\d+$/);
|
|
265
|
-
expect(receivedContext.roomId).toBe("lobby");
|
|
266
|
-
expect(receivedContext.message).toEqual({ content: "hello" });
|
|
267
|
-
expect(receivedContext.hasReply).toBe(true);
|
|
268
|
-
|
|
269
|
-
ws.close();
|
|
270
|
-
await alepha.stop();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("invalid message returns error to client", async ({ expect }) => {
|
|
274
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
275
|
-
|
|
276
|
-
class Controller {
|
|
277
|
-
ch = $channel({
|
|
278
|
-
path: "/ws/validate",
|
|
279
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
ws = $websocket({
|
|
283
|
-
channel: this.ch,
|
|
284
|
-
handler: async () => {},
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
alepha.inject(Controller);
|
|
289
|
-
await alepha.start();
|
|
290
|
-
|
|
291
|
-
const hostname = alepha
|
|
292
|
-
.inject(NodeHttpServerProvider)
|
|
293
|
-
.hostname.replace("http://", "ws://");
|
|
294
|
-
|
|
295
|
-
const ws = new WebSocket(`${hostname}/ws/validate?roomId=test`);
|
|
296
|
-
await waitForOpen(ws);
|
|
297
|
-
await delay(50);
|
|
298
|
-
|
|
299
|
-
// Send message that doesn't match the out schema (missing "content")
|
|
300
|
-
const errorPromise = waitForMessage(ws);
|
|
301
|
-
ws.send(
|
|
302
|
-
JSON.stringify({ roomId: "test", message: { invalid: "field" } }),
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
const response = await errorPromise;
|
|
306
|
-
expect(response.error).toBeDefined();
|
|
307
|
-
|
|
308
|
-
ws.close();
|
|
309
|
-
await alepha.stop();
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test("non-JSON message is ignored", async ({ expect }) => {
|
|
313
|
-
let handlerCalled = false;
|
|
314
|
-
|
|
315
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
316
|
-
|
|
317
|
-
class Controller {
|
|
318
|
-
ch = $channel({
|
|
319
|
-
path: "/ws/nonjson",
|
|
320
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
ws = $websocket({
|
|
324
|
-
channel: this.ch,
|
|
325
|
-
handler: async () => {
|
|
326
|
-
handlerCalled = true;
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
alepha.inject(Controller);
|
|
332
|
-
await alepha.start();
|
|
333
|
-
|
|
334
|
-
const hostname = alepha
|
|
335
|
-
.inject(NodeHttpServerProvider)
|
|
336
|
-
.hostname.replace("http://", "ws://");
|
|
337
|
-
|
|
338
|
-
const ws = new WebSocket(`${hostname}/ws/nonjson?roomId=test`);
|
|
339
|
-
await waitForOpen(ws);
|
|
340
|
-
await delay(50);
|
|
341
|
-
|
|
342
|
-
ws.send("not json {{{");
|
|
343
|
-
await delay(200);
|
|
344
|
-
|
|
345
|
-
expect(handlerCalled).toBe(false);
|
|
346
|
-
|
|
347
|
-
ws.close();
|
|
348
|
-
await alepha.stop();
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
353
|
-
// Server emit targeting
|
|
354
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
355
|
-
|
|
356
|
-
describe("emit targeting", () => {
|
|
357
|
-
test("emit to all connections (no targeting)", async ({ expect }) => {
|
|
358
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
359
|
-
|
|
360
|
-
class Controller {
|
|
361
|
-
ch = $channel({
|
|
362
|
-
path: "/ws/broadcast-all",
|
|
363
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
ws = $websocket({
|
|
367
|
-
channel: this.ch,
|
|
368
|
-
handler: async () => {},
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const controller = alepha.inject(Controller);
|
|
373
|
-
await alepha.start();
|
|
374
|
-
|
|
375
|
-
const hostname = alepha
|
|
376
|
-
.inject(NodeHttpServerProvider)
|
|
377
|
-
.hostname.replace("http://", "ws://");
|
|
378
|
-
|
|
379
|
-
const ws1 = new WebSocket(`${hostname}/ws/broadcast-all?roomId=a`);
|
|
380
|
-
const ws2 = new WebSocket(`${hostname}/ws/broadcast-all?roomId=b`);
|
|
381
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2)]);
|
|
382
|
-
|
|
383
|
-
const msgs1: any[] = [];
|
|
384
|
-
const msgs2: any[] = [];
|
|
385
|
-
collectMessages(ws1, msgs1);
|
|
386
|
-
collectMessages(ws2, msgs2);
|
|
387
|
-
await delay(50);
|
|
388
|
-
|
|
389
|
-
// Emit with no room/user/connection targeting → goes to all
|
|
390
|
-
await controller.ws.emit({
|
|
391
|
-
message: { type: "announce", content: "global" },
|
|
392
|
-
});
|
|
393
|
-
await delay(200);
|
|
394
|
-
|
|
395
|
-
expect(msgs1).toHaveLength(1);
|
|
396
|
-
expect(msgs2).toHaveLength(1);
|
|
397
|
-
expect(msgs1[0].content).toBe("global");
|
|
398
|
-
expect(msgs2[0].content).toBe("global");
|
|
399
|
-
|
|
400
|
-
ws1.close();
|
|
401
|
-
ws2.close();
|
|
402
|
-
await alepha.stop();
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
test("emit to specific room only", async ({ expect }) => {
|
|
406
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
407
|
-
|
|
408
|
-
class Controller {
|
|
409
|
-
ch = $channel({
|
|
410
|
-
path: "/ws/room-target",
|
|
411
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
ws = $websocket({
|
|
415
|
-
channel: this.ch,
|
|
416
|
-
handler: async () => {},
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const controller = alepha.inject(Controller);
|
|
421
|
-
await alepha.start();
|
|
422
|
-
|
|
423
|
-
const hostname = alepha
|
|
424
|
-
.inject(NodeHttpServerProvider)
|
|
425
|
-
.hostname.replace("http://", "ws://");
|
|
426
|
-
|
|
427
|
-
const wsA = new WebSocket(`${hostname}/ws/room-target?roomId=room-a`);
|
|
428
|
-
const wsB = new WebSocket(`${hostname}/ws/room-target?roomId=room-b`);
|
|
429
|
-
await Promise.all([waitForOpen(wsA), waitForOpen(wsB)]);
|
|
430
|
-
|
|
431
|
-
const msgsA: any[] = [];
|
|
432
|
-
const msgsB: any[] = [];
|
|
433
|
-
collectMessages(wsA, msgsA);
|
|
434
|
-
collectMessages(wsB, msgsB);
|
|
435
|
-
await delay(50);
|
|
436
|
-
|
|
437
|
-
await controller.ws.emit({
|
|
438
|
-
roomId: "room-a",
|
|
439
|
-
message: { type: "targeted", content: "for room-a" },
|
|
440
|
-
});
|
|
441
|
-
await delay(200);
|
|
442
|
-
|
|
443
|
-
expect(msgsA).toHaveLength(1);
|
|
444
|
-
expect(msgsA[0].content).toBe("for room-a");
|
|
445
|
-
expect(msgsB).toHaveLength(0);
|
|
446
|
-
|
|
447
|
-
wsA.close();
|
|
448
|
-
wsB.close();
|
|
449
|
-
await alepha.stop();
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
test("emit to multiple rooms", async ({ expect }) => {
|
|
453
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
454
|
-
|
|
455
|
-
class Controller {
|
|
456
|
-
ch = $channel({
|
|
457
|
-
path: "/ws/multi-room-emit",
|
|
458
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
ws = $websocket({
|
|
462
|
-
channel: this.ch,
|
|
463
|
-
handler: async () => {},
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const controller = alepha.inject(Controller);
|
|
468
|
-
await alepha.start();
|
|
469
|
-
|
|
470
|
-
const hostname = alepha
|
|
471
|
-
.inject(NodeHttpServerProvider)
|
|
472
|
-
.hostname.replace("http://", "ws://");
|
|
473
|
-
|
|
474
|
-
const ws1 = new WebSocket(`${hostname}/ws/multi-room-emit?roomId=room-1`);
|
|
475
|
-
const ws2 = new WebSocket(`${hostname}/ws/multi-room-emit?roomId=room-2`);
|
|
476
|
-
const ws3 = new WebSocket(`${hostname}/ws/multi-room-emit?roomId=room-3`);
|
|
477
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2), waitForOpen(ws3)]);
|
|
478
|
-
|
|
479
|
-
const msgs1: any[] = [];
|
|
480
|
-
const msgs2: any[] = [];
|
|
481
|
-
const msgs3: any[] = [];
|
|
482
|
-
collectMessages(ws1, msgs1);
|
|
483
|
-
collectMessages(ws2, msgs2);
|
|
484
|
-
collectMessages(ws3, msgs3);
|
|
485
|
-
await delay(50);
|
|
486
|
-
|
|
487
|
-
await controller.ws.emit({
|
|
488
|
-
roomIds: ["room-1", "room-2"],
|
|
489
|
-
message: { type: "multi", content: "for 1 and 2" },
|
|
490
|
-
});
|
|
491
|
-
await delay(200);
|
|
492
|
-
|
|
493
|
-
expect(msgs1).toHaveLength(1);
|
|
494
|
-
expect(msgs2).toHaveLength(1);
|
|
495
|
-
expect(msgs3).toHaveLength(0);
|
|
496
|
-
|
|
497
|
-
ws1.close();
|
|
498
|
-
ws2.close();
|
|
499
|
-
ws3.close();
|
|
500
|
-
await alepha.stop();
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
test("emit with exceptConnectionIds", async ({ expect }) => {
|
|
504
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
505
|
-
let capturedConnId = "";
|
|
506
|
-
|
|
507
|
-
class Controller {
|
|
508
|
-
ch = $channel({
|
|
509
|
-
path: "/ws/except-conn",
|
|
510
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
ws = $websocket({
|
|
514
|
-
channel: this.ch,
|
|
515
|
-
handler: async () => {},
|
|
516
|
-
onConnect: ({ connectionId }) => {
|
|
517
|
-
// Capture the first connection ID
|
|
518
|
-
if (!capturedConnId) capturedConnId = connectionId;
|
|
519
|
-
},
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const controller = alepha.inject(Controller);
|
|
524
|
-
await alepha.start();
|
|
525
|
-
|
|
526
|
-
const hostname = alepha
|
|
527
|
-
.inject(NodeHttpServerProvider)
|
|
528
|
-
.hostname.replace("http://", "ws://");
|
|
529
|
-
|
|
530
|
-
const ws1 = new WebSocket(`${hostname}/ws/except-conn?roomId=room`);
|
|
531
|
-
await waitForOpen(ws1);
|
|
532
|
-
await delay(100); // ensure server-side onConnect fires for ws1 first
|
|
533
|
-
|
|
534
|
-
const ws2 = new WebSocket(`${hostname}/ws/except-conn?roomId=room`);
|
|
535
|
-
await waitForOpen(ws2);
|
|
536
|
-
await delay(100);
|
|
537
|
-
|
|
538
|
-
const msgs1: any[] = [];
|
|
539
|
-
const msgs2: any[] = [];
|
|
540
|
-
collectMessages(ws1, msgs1);
|
|
541
|
-
collectMessages(ws2, msgs2);
|
|
542
|
-
|
|
543
|
-
// Exclude ws1 from the broadcast
|
|
544
|
-
await controller.ws.emit({
|
|
545
|
-
roomId: "room",
|
|
546
|
-
exceptConnectionIds: [capturedConnId],
|
|
547
|
-
message: { type: "selective", content: "not for ws1" },
|
|
548
|
-
});
|
|
549
|
-
await delay(200);
|
|
550
|
-
|
|
551
|
-
expect(msgs1).toHaveLength(0);
|
|
552
|
-
expect(msgs2).toHaveLength(1);
|
|
553
|
-
|
|
554
|
-
ws1.close();
|
|
555
|
-
ws2.close();
|
|
556
|
-
await alepha.stop();
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
test("emit to specific connectionId", async ({ expect }) => {
|
|
560
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
561
|
-
let targetConnId = "";
|
|
562
|
-
|
|
563
|
-
class Controller {
|
|
564
|
-
ch = $channel({
|
|
565
|
-
path: "/ws/conn-target",
|
|
566
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
ws = $websocket({
|
|
570
|
-
channel: this.ch,
|
|
571
|
-
handler: async () => {},
|
|
572
|
-
onConnect: ({ connectionId }) => {
|
|
573
|
-
targetConnId = connectionId;
|
|
574
|
-
},
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const controller = alepha.inject(Controller);
|
|
579
|
-
await alepha.start();
|
|
580
|
-
|
|
581
|
-
const hostname = alepha
|
|
582
|
-
.inject(NodeHttpServerProvider)
|
|
583
|
-
.hostname.replace("http://", "ws://");
|
|
584
|
-
|
|
585
|
-
const ws1 = new WebSocket(`${hostname}/ws/conn-target?roomId=room`);
|
|
586
|
-
await waitForOpen(ws1);
|
|
587
|
-
await delay(100);
|
|
588
|
-
|
|
589
|
-
const savedConnId = targetConnId;
|
|
590
|
-
|
|
591
|
-
const ws2 = new WebSocket(`${hostname}/ws/conn-target?roomId=room`);
|
|
592
|
-
await waitForOpen(ws2);
|
|
593
|
-
await delay(50);
|
|
594
|
-
|
|
595
|
-
const msgs1: any[] = [];
|
|
596
|
-
const msgs2: any[] = [];
|
|
597
|
-
collectMessages(ws1, msgs1);
|
|
598
|
-
collectMessages(ws2, msgs2);
|
|
599
|
-
|
|
600
|
-
// Emit only to ws1's connectionId
|
|
601
|
-
await controller.ws.emit({
|
|
602
|
-
connectionId: savedConnId,
|
|
603
|
-
message: { type: "direct", content: "only for ws1" },
|
|
604
|
-
});
|
|
605
|
-
await delay(200);
|
|
606
|
-
|
|
607
|
-
expect(msgs1).toHaveLength(1);
|
|
608
|
-
expect(msgs1[0].content).toBe("only for ws1");
|
|
609
|
-
expect(msgs2).toHaveLength(0);
|
|
610
|
-
|
|
611
|
-
ws1.close();
|
|
612
|
-
ws2.close();
|
|
613
|
-
await alepha.stop();
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
618
|
-
// Reply from handler
|
|
619
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
620
|
-
|
|
621
|
-
describe("reply from handler", () => {
|
|
622
|
-
test("reply broadcasts to room", async ({ expect }) => {
|
|
623
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
624
|
-
|
|
625
|
-
class Controller {
|
|
626
|
-
ch = $channel({
|
|
627
|
-
path: "/ws/reply-broadcast",
|
|
628
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
ws = $websocket({
|
|
632
|
-
channel: this.ch,
|
|
633
|
-
handler: async ({ message, reply }) => {
|
|
634
|
-
await reply({
|
|
635
|
-
message: { type: "echo", content: message.content },
|
|
636
|
-
});
|
|
637
|
-
},
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
alepha.inject(Controller);
|
|
642
|
-
await alepha.start();
|
|
643
|
-
|
|
644
|
-
const hostname = alepha
|
|
645
|
-
.inject(NodeHttpServerProvider)
|
|
646
|
-
.hostname.replace("http://", "ws://");
|
|
647
|
-
|
|
648
|
-
const ws1 = new WebSocket(`${hostname}/ws/reply-broadcast?roomId=room`);
|
|
649
|
-
const ws2 = new WebSocket(`${hostname}/ws/reply-broadcast?roomId=room`);
|
|
650
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2)]);
|
|
651
|
-
|
|
652
|
-
const msgs1: any[] = [];
|
|
653
|
-
const msgs2: any[] = [];
|
|
654
|
-
collectMessages(ws1, msgs1);
|
|
655
|
-
collectMessages(ws2, msgs2);
|
|
656
|
-
await delay(50);
|
|
657
|
-
|
|
658
|
-
ws1.send(JSON.stringify({ roomId: "room", message: { content: "hi" } }));
|
|
659
|
-
await delay(200);
|
|
660
|
-
|
|
661
|
-
// Both should receive the reply (no exceptSelf)
|
|
662
|
-
expect(msgs1).toHaveLength(1);
|
|
663
|
-
expect(msgs2).toHaveLength(1);
|
|
664
|
-
expect(msgs1[0].content).toBe("hi");
|
|
665
|
-
|
|
666
|
-
ws1.close();
|
|
667
|
-
ws2.close();
|
|
668
|
-
await alepha.stop();
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
test("reply with exceptSelf excludes sender", async ({ expect }) => {
|
|
672
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
673
|
-
|
|
674
|
-
class Controller {
|
|
675
|
-
ch = $channel({
|
|
676
|
-
path: "/ws/reply-except",
|
|
677
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
ws = $websocket({
|
|
681
|
-
channel: this.ch,
|
|
682
|
-
handler: async ({ message, reply }) => {
|
|
683
|
-
await reply({
|
|
684
|
-
message: { type: "echo", content: message.content },
|
|
685
|
-
exceptSelf: true,
|
|
686
|
-
});
|
|
687
|
-
},
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
alepha.inject(Controller);
|
|
692
|
-
await alepha.start();
|
|
693
|
-
|
|
694
|
-
const hostname = alepha
|
|
695
|
-
.inject(NodeHttpServerProvider)
|
|
696
|
-
.hostname.replace("http://", "ws://");
|
|
697
|
-
|
|
698
|
-
const ws1 = new WebSocket(`${hostname}/ws/reply-except?roomId=room`);
|
|
699
|
-
const ws2 = new WebSocket(`${hostname}/ws/reply-except?roomId=room`);
|
|
700
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2)]);
|
|
701
|
-
|
|
702
|
-
const msgs1: any[] = [];
|
|
703
|
-
const msgs2: any[] = [];
|
|
704
|
-
collectMessages(ws1, msgs1);
|
|
705
|
-
collectMessages(ws2, msgs2);
|
|
706
|
-
await delay(50);
|
|
707
|
-
|
|
708
|
-
ws1.send(
|
|
709
|
-
JSON.stringify({ roomId: "room", message: { content: "hello" } }),
|
|
710
|
-
);
|
|
711
|
-
await delay(200);
|
|
712
|
-
|
|
713
|
-
// ws1 (sender) should NOT receive, ws2 should
|
|
714
|
-
expect(msgs1).toHaveLength(0);
|
|
715
|
-
expect(msgs2).toHaveLength(1);
|
|
716
|
-
expect(msgs2[0].content).toBe("hello");
|
|
717
|
-
|
|
718
|
-
ws1.close();
|
|
719
|
-
ws2.close();
|
|
720
|
-
await alepha.stop();
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
test("reply to a different room", async ({ expect }) => {
|
|
724
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
725
|
-
|
|
726
|
-
class Controller {
|
|
727
|
-
ch = $channel({
|
|
728
|
-
path: "/ws/reply-room",
|
|
729
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
ws = $websocket({
|
|
733
|
-
channel: this.ch,
|
|
734
|
-
handler: async ({ message, reply }) => {
|
|
735
|
-
// Reply to room-b instead of the sender's room
|
|
736
|
-
await reply({
|
|
737
|
-
roomId: "room-b",
|
|
738
|
-
message: { type: "forwarded", content: message.content },
|
|
739
|
-
});
|
|
740
|
-
},
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
alepha.inject(Controller);
|
|
745
|
-
await alepha.start();
|
|
746
|
-
|
|
747
|
-
const hostname = alepha
|
|
748
|
-
.inject(NodeHttpServerProvider)
|
|
749
|
-
.hostname.replace("http://", "ws://");
|
|
750
|
-
|
|
751
|
-
const wsA = new WebSocket(`${hostname}/ws/reply-room?roomId=room-a`);
|
|
752
|
-
const wsB = new WebSocket(`${hostname}/ws/reply-room?roomId=room-b`);
|
|
753
|
-
await Promise.all([waitForOpen(wsA), waitForOpen(wsB)]);
|
|
754
|
-
|
|
755
|
-
const msgsA: any[] = [];
|
|
756
|
-
const msgsB: any[] = [];
|
|
757
|
-
collectMessages(wsA, msgsA);
|
|
758
|
-
collectMessages(wsB, msgsB);
|
|
759
|
-
await delay(50);
|
|
760
|
-
|
|
761
|
-
wsA.send(
|
|
762
|
-
JSON.stringify({
|
|
763
|
-
roomId: "room-a",
|
|
764
|
-
message: { content: "forward me" },
|
|
765
|
-
}),
|
|
766
|
-
);
|
|
767
|
-
await delay(200);
|
|
768
|
-
|
|
769
|
-
// room-a sender should not receive, room-b should
|
|
770
|
-
expect(msgsA).toHaveLength(0);
|
|
771
|
-
expect(msgsB).toHaveLength(1);
|
|
772
|
-
expect(msgsB[0].content).toBe("forward me");
|
|
773
|
-
|
|
774
|
-
wsA.close();
|
|
775
|
-
wsB.close();
|
|
776
|
-
await alepha.stop();
|
|
777
|
-
});
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
781
|
-
// Provider API
|
|
782
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
783
|
-
|
|
784
|
-
describe("provider API", () => {
|
|
785
|
-
test("getConnections returns all active connections", async ({
|
|
786
|
-
expect,
|
|
787
|
-
}) => {
|
|
788
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
789
|
-
|
|
790
|
-
class Controller {
|
|
791
|
-
ch = $channel({
|
|
792
|
-
path: "/ws/get-conns",
|
|
793
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
ws = $websocket({
|
|
797
|
-
channel: this.ch,
|
|
798
|
-
handler: async () => {},
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
alepha.inject(Controller);
|
|
803
|
-
await alepha.start();
|
|
804
|
-
|
|
805
|
-
const hostname = alepha
|
|
806
|
-
.inject(NodeHttpServerProvider)
|
|
807
|
-
.hostname.replace("http://", "ws://");
|
|
808
|
-
const provider = alepha.inject(NodeWebSocketServerProvider);
|
|
809
|
-
|
|
810
|
-
const ws1 = new WebSocket(`${hostname}/ws/get-conns?roomId=room`);
|
|
811
|
-
const ws2 = new WebSocket(`${hostname}/ws/get-conns?roomId=room`);
|
|
812
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2)]);
|
|
813
|
-
await delay(50);
|
|
814
|
-
|
|
815
|
-
expect(provider.getConnections()).toHaveLength(2);
|
|
816
|
-
|
|
817
|
-
ws1.close();
|
|
818
|
-
await delay(100);
|
|
819
|
-
|
|
820
|
-
expect(provider.getConnections()).toHaveLength(1);
|
|
821
|
-
|
|
822
|
-
ws2.close();
|
|
823
|
-
await delay(100);
|
|
824
|
-
|
|
825
|
-
expect(provider.getConnections()).toHaveLength(0);
|
|
826
|
-
|
|
827
|
-
await alepha.stop();
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
test("getRoomConnections returns connections in specific room", async ({
|
|
831
|
-
expect,
|
|
832
|
-
}) => {
|
|
833
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
834
|
-
|
|
835
|
-
class Controller {
|
|
836
|
-
ch = $channel({
|
|
837
|
-
path: "/ws/room-conns",
|
|
838
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
ws = $websocket({
|
|
842
|
-
channel: this.ch,
|
|
843
|
-
handler: async () => {},
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
alepha.inject(Controller);
|
|
848
|
-
await alepha.start();
|
|
849
|
-
|
|
850
|
-
const hostname = alepha
|
|
851
|
-
.inject(NodeHttpServerProvider)
|
|
852
|
-
.hostname.replace("http://", "ws://");
|
|
853
|
-
const provider = alepha.inject(NodeWebSocketServerProvider);
|
|
854
|
-
|
|
855
|
-
const ws1 = new WebSocket(`${hostname}/ws/room-conns?roomId=vip`);
|
|
856
|
-
const ws2 = new WebSocket(`${hostname}/ws/room-conns?roomId=general`);
|
|
857
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2)]);
|
|
858
|
-
await delay(50);
|
|
859
|
-
|
|
860
|
-
expect(provider.getRoomConnections("vip")).toHaveLength(1);
|
|
861
|
-
expect(provider.getRoomConnections("general")).toHaveLength(1);
|
|
862
|
-
expect(provider.getRoomConnections("nonexistent")).toHaveLength(0);
|
|
863
|
-
|
|
864
|
-
ws1.close();
|
|
865
|
-
ws2.close();
|
|
866
|
-
await alepha.stop();
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
test("closeConnection closes a specific connection", async ({ expect }) => {
|
|
870
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
871
|
-
let connId = "";
|
|
872
|
-
|
|
873
|
-
class Controller {
|
|
874
|
-
ch = $channel({
|
|
875
|
-
path: "/ws/close-conn",
|
|
876
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
ws = $websocket({
|
|
880
|
-
channel: this.ch,
|
|
881
|
-
handler: async () => {},
|
|
882
|
-
onConnect: ({ connectionId }) => {
|
|
883
|
-
connId = connectionId;
|
|
884
|
-
},
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
alepha.inject(Controller);
|
|
889
|
-
await alepha.start();
|
|
890
|
-
|
|
891
|
-
const hostname = alepha
|
|
892
|
-
.inject(NodeHttpServerProvider)
|
|
893
|
-
.hostname.replace("http://", "ws://");
|
|
894
|
-
const provider = alepha.inject(NodeWebSocketServerProvider);
|
|
895
|
-
|
|
896
|
-
const ws = new WebSocket(`${hostname}/ws/close-conn?roomId=room`);
|
|
897
|
-
await waitForOpen(ws);
|
|
898
|
-
await delay(100);
|
|
899
|
-
|
|
900
|
-
const closePromise = waitForClose(ws);
|
|
901
|
-
await provider.closeConnection(connId, 4000, "kicked");
|
|
902
|
-
|
|
903
|
-
const { code } = await closePromise;
|
|
904
|
-
expect(code).toBe(4000);
|
|
905
|
-
|
|
906
|
-
await alepha.stop();
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
911
|
-
// Graceful shutdown
|
|
912
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
913
|
-
|
|
914
|
-
describe("graceful shutdown", () => {
|
|
915
|
-
test("stop closes all connections", async ({ expect }) => {
|
|
916
|
-
const alepha = Alepha.create().with(AlephaWebSocket);
|
|
917
|
-
|
|
918
|
-
class Controller {
|
|
919
|
-
ch = $channel({
|
|
920
|
-
path: "/ws/shutdown",
|
|
921
|
-
schema: { in: chatInSchema, out: chatOutSchema },
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
ws = $websocket({
|
|
925
|
-
channel: this.ch,
|
|
926
|
-
handler: async () => {},
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
alepha.inject(Controller);
|
|
931
|
-
await alepha.start();
|
|
932
|
-
|
|
933
|
-
const hostname = alepha
|
|
934
|
-
.inject(NodeHttpServerProvider)
|
|
935
|
-
.hostname.replace("http://", "ws://");
|
|
936
|
-
|
|
937
|
-
const ws1 = new WebSocket(`${hostname}/ws/shutdown?roomId=room`);
|
|
938
|
-
const ws2 = new WebSocket(`${hostname}/ws/shutdown?roomId=room`);
|
|
939
|
-
await Promise.all([waitForOpen(ws1), waitForOpen(ws2)]);
|
|
940
|
-
|
|
941
|
-
const close1 = waitForClose(ws1);
|
|
942
|
-
const close2 = waitForClose(ws2);
|
|
943
|
-
|
|
944
|
-
await alepha.stop();
|
|
945
|
-
|
|
946
|
-
const [result1, result2] = await Promise.all([close1, close2]);
|
|
947
|
-
expect(result1.code).toBe(1001);
|
|
948
|
-
expect(result2.code).toBe(1001);
|
|
949
|
-
});
|
|
950
|
-
});
|
|
951
|
-
});
|