@crowdedkingdomstudios/crowdyjs 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +26 -0
- package/README.md +110 -772
- package/dist/auth-state.d.ts +6 -16
- package/dist/auth-state.d.ts.map +1 -1
- package/dist/auth-state.js +9 -26
- package/dist/client.d.ts +14 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +22 -17
- package/dist/crowdy-client.d.ts +21 -3
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +37 -20
- package/dist/domains/serverStatus.d.ts +2 -1
- package/dist/domains/serverStatus.d.ts.map +1 -1
- package/dist/domains/serverStatus.js +5 -1
- package/dist/domains/udp.d.ts +19 -2
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +38 -0
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/generated/graphql.d.ts +404 -12
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +2 -0
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +1 -0
- package/dist/realtime.d.ts +86 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +270 -0
- package/dist/session.d.ts +27 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +61 -0
- package/dist/subscriptions.d.ts +1 -48
- package/dist/subscriptions.d.ts.map +1 -1
- package/dist/subscriptions.js +1 -192
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +50 -0
- package/dist/world.d.ts +35 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +88 -0
- package/package.json +13 -3
package/dist/session.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export class BrowserLocalStorageTokenStore {
|
|
2
|
+
constructor(key = 'crowdyjs:token') {
|
|
3
|
+
this.key = key;
|
|
4
|
+
}
|
|
5
|
+
get() {
|
|
6
|
+
if (typeof localStorage === 'undefined')
|
|
7
|
+
return null;
|
|
8
|
+
return localStorage.getItem(this.key);
|
|
9
|
+
}
|
|
10
|
+
set(token) {
|
|
11
|
+
if (typeof localStorage === 'undefined')
|
|
12
|
+
return;
|
|
13
|
+
localStorage.setItem(this.key, token);
|
|
14
|
+
}
|
|
15
|
+
clear() {
|
|
16
|
+
if (typeof localStorage === 'undefined')
|
|
17
|
+
return;
|
|
18
|
+
localStorage.removeItem(this.key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class SessionStore {
|
|
22
|
+
constructor(tokenStore) {
|
|
23
|
+
this.tokenStore = tokenStore;
|
|
24
|
+
this.token = null;
|
|
25
|
+
this.listeners = new Set();
|
|
26
|
+
}
|
|
27
|
+
async restore() {
|
|
28
|
+
const token = (await this.tokenStore?.get()) ?? null;
|
|
29
|
+
this.setToken(token, { persist: false });
|
|
30
|
+
return token;
|
|
31
|
+
}
|
|
32
|
+
getToken() {
|
|
33
|
+
return this.token;
|
|
34
|
+
}
|
|
35
|
+
setToken(token, options = {}) {
|
|
36
|
+
if (token === this.token)
|
|
37
|
+
return;
|
|
38
|
+
this.token = token;
|
|
39
|
+
if (options.persist !== false) {
|
|
40
|
+
if (token) {
|
|
41
|
+
void this.tokenStore?.set(token);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
void this.tokenStore?.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const listener of [...this.listeners]) {
|
|
48
|
+
listener(token);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
this.setToken(null);
|
|
53
|
+
}
|
|
54
|
+
onChange(listener) {
|
|
55
|
+
this.listeners.add(listener);
|
|
56
|
+
listener(this.token);
|
|
57
|
+
return () => {
|
|
58
|
+
this.listeners.delete(listener);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/subscriptions.d.ts
CHANGED
|
@@ -1,49 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* WebSocket subscription manager for the udpNotifications stream.
|
|
3
|
-
*
|
|
4
|
-
* Owns one shared `graphql-transport-ws` socket and dispatches incoming
|
|
5
|
-
* `udpNotifications` payloads to per-typename handler arrays. Reads its
|
|
6
|
-
* bearer token from `AuthState` so HTTP and WS auth can never drift.
|
|
7
|
-
*
|
|
8
|
-
* Public API is now `subscribe(handlers)` which returns an unsubscribe
|
|
9
|
-
* function; the per-handler `onActorUpdate` etc. shims are gone.
|
|
10
|
-
*/
|
|
11
|
-
import { AuthState } from './auth-state.js';
|
|
12
|
-
import type { ActorUpdateHandler, ActorUpdateResponseHandler, VoxelUpdateHandler, VoxelUpdateResponseHandler, ClientAudioHandler, ClientTextHandler, ClientEventHandler, ServerEventHandler, GenericErrorHandler, UnsubscribeFn } from './types.js';
|
|
13
|
-
export interface UdpNotificationHandlers {
|
|
14
|
-
onActorUpdate?: ActorUpdateHandler;
|
|
15
|
-
onActorUpdateResponse?: ActorUpdateResponseHandler;
|
|
16
|
-
onVoxelUpdate?: VoxelUpdateHandler;
|
|
17
|
-
onVoxelUpdateResponse?: VoxelUpdateResponseHandler;
|
|
18
|
-
onClientAudio?: ClientAudioHandler;
|
|
19
|
-
onClientText?: ClientTextHandler;
|
|
20
|
-
onClientEvent?: ClientEventHandler;
|
|
21
|
-
onServerEvent?: ServerEventHandler;
|
|
22
|
-
onGenericError?: GenericErrorHandler;
|
|
23
|
-
}
|
|
24
|
-
export interface SubscriptionManagerConfig {
|
|
25
|
-
wsEndpoint?: string;
|
|
26
|
-
}
|
|
27
|
-
export declare class SubscriptionManager {
|
|
28
|
-
private readonly wsEndpoint;
|
|
29
|
-
private readonly authState;
|
|
30
|
-
private wsClient;
|
|
31
|
-
private wsUnsubscribe;
|
|
32
|
-
private subscriptionId;
|
|
33
|
-
private subscribers;
|
|
34
|
-
private nextSubscriberId;
|
|
35
|
-
constructor(config: SubscriptionManagerConfig | undefined, authState: AuthState);
|
|
36
|
-
/**
|
|
37
|
-
* Register handlers for the udpNotifications stream. The first call opens
|
|
38
|
-
* the shared socket (provided we have a token); subsequent calls reuse it.
|
|
39
|
-
* The returned function detaches just this set of handlers and closes the
|
|
40
|
-
* socket once the last subscriber leaves.
|
|
41
|
-
*/
|
|
42
|
-
subscribe(handlers: UdpNotificationHandlers): UnsubscribeFn;
|
|
43
|
-
private ensureSubscription;
|
|
44
|
-
private startSubscription;
|
|
45
|
-
private dispatch;
|
|
46
|
-
private checkIfShouldUnsubscribe;
|
|
47
|
-
close(): void;
|
|
48
|
-
}
|
|
1
|
+
export { RealtimeClient as SubscriptionManager, type RealtimeConfig as SubscriptionManagerConfig, type UdpNotificationHandlers, } from './realtime.js';
|
|
49
2
|
//# sourceMappingURL=subscriptions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,IAAI,mBAAmB,EACrC,KAAK,cAAc,IAAI,yBAAyB,EAChD,KAAK,uBAAuB,GAC7B,MAAM,eAAe,CAAC"}
|
package/dist/subscriptions.js
CHANGED
|
@@ -1,192 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* WebSocket subscription manager for the udpNotifications stream.
|
|
3
|
-
*
|
|
4
|
-
* Owns one shared `graphql-transport-ws` socket and dispatches incoming
|
|
5
|
-
* `udpNotifications` payloads to per-typename handler arrays. Reads its
|
|
6
|
-
* bearer token from `AuthState` so HTTP and WS auth can never drift.
|
|
7
|
-
*
|
|
8
|
-
* Public API is now `subscribe(handlers)` which returns an unsubscribe
|
|
9
|
-
* function; the per-handler `onActorUpdate` etc. shims are gone.
|
|
10
|
-
*/
|
|
11
|
-
export class SubscriptionManager {
|
|
12
|
-
constructor(config = {}, authState) {
|
|
13
|
-
this.wsClient = null;
|
|
14
|
-
this.wsUnsubscribe = null;
|
|
15
|
-
this.subscriptionId = null;
|
|
16
|
-
this.subscribers = new Map();
|
|
17
|
-
this.nextSubscriberId = 1;
|
|
18
|
-
this.wsEndpoint = config.wsEndpoint || 'ws://localhost:3000/graphql';
|
|
19
|
-
this.authState = authState;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Register handlers for the udpNotifications stream. The first call opens
|
|
23
|
-
* the shared socket (provided we have a token); subsequent calls reuse it.
|
|
24
|
-
* The returned function detaches just this set of handlers and closes the
|
|
25
|
-
* socket once the last subscriber leaves.
|
|
26
|
-
*/
|
|
27
|
-
subscribe(handlers) {
|
|
28
|
-
const id = `s${this.nextSubscriberId++}`;
|
|
29
|
-
this.subscribers.set(id, { id, handlers });
|
|
30
|
-
this.ensureSubscription();
|
|
31
|
-
return () => {
|
|
32
|
-
this.subscribers.delete(id);
|
|
33
|
-
this.checkIfShouldUnsubscribe();
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
ensureSubscription() {
|
|
37
|
-
if (!this.wsClient ||
|
|
38
|
-
(this.wsClient.readyState !== WebSocket.OPEN &&
|
|
39
|
-
this.wsClient.readyState !== WebSocket.CONNECTING)) {
|
|
40
|
-
this.startSubscription();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
startSubscription() {
|
|
44
|
-
const token = this.authState.getToken();
|
|
45
|
-
if (!token) {
|
|
46
|
-
throw new Error('Must be authenticated to subscribe');
|
|
47
|
-
}
|
|
48
|
-
this.subscriptionId = `udp-notifications-${Date.now()}`;
|
|
49
|
-
const ws = new WebSocket(this.wsEndpoint, 'graphql-transport-ws');
|
|
50
|
-
this.wsClient = ws;
|
|
51
|
-
ws.onopen = () => {
|
|
52
|
-
ws.send(JSON.stringify({
|
|
53
|
-
type: 'connection_init',
|
|
54
|
-
payload: {
|
|
55
|
-
Authorization: `Bearer ${token}`,
|
|
56
|
-
},
|
|
57
|
-
}));
|
|
58
|
-
};
|
|
59
|
-
ws.onmessage = (event) => {
|
|
60
|
-
try {
|
|
61
|
-
const message = JSON.parse(typeof event.data === 'string' ? event.data : '');
|
|
62
|
-
if (message.type === 'connection_ack') {
|
|
63
|
-
ws.send(JSON.stringify({
|
|
64
|
-
id: this.subscriptionId,
|
|
65
|
-
type: 'subscribe',
|
|
66
|
-
payload: { query: UDP_NOTIFICATIONS_QUERY },
|
|
67
|
-
}));
|
|
68
|
-
}
|
|
69
|
-
else if (message.type === 'next') {
|
|
70
|
-
if (message.payload?.data?.udpNotifications === null)
|
|
71
|
-
return;
|
|
72
|
-
const notification = message.payload?.data
|
|
73
|
-
?.udpNotifications;
|
|
74
|
-
if (notification) {
|
|
75
|
-
this.dispatch(notification);
|
|
76
|
-
}
|
|
77
|
-
else if (message.payload?.errors) {
|
|
78
|
-
console.error('Subscription errors:', message.payload.errors);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
else if (message.type === 'error') {
|
|
82
|
-
console.error('Subscription error:', message.payload);
|
|
83
|
-
}
|
|
84
|
-
else if (message.type === 'complete') {
|
|
85
|
-
if (this.wsClient === ws)
|
|
86
|
-
this.wsClient = null;
|
|
87
|
-
}
|
|
88
|
-
else if (message.type === 'ping') {
|
|
89
|
-
ws.send(JSON.stringify({ type: 'pong' }));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
console.error('Error parsing WebSocket message:', error);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
ws.onerror = (error) => {
|
|
97
|
-
console.error('WebSocket error:', error);
|
|
98
|
-
};
|
|
99
|
-
ws.onclose = (event) => {
|
|
100
|
-
if (event.code !== 1000) {
|
|
101
|
-
console.warn(`WebSocket closed unexpectedly: ${event.reason || event.code}`);
|
|
102
|
-
}
|
|
103
|
-
if (this.wsClient === ws)
|
|
104
|
-
this.wsClient = null;
|
|
105
|
-
};
|
|
106
|
-
this.wsUnsubscribe = () => {
|
|
107
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
108
|
-
if (this.subscriptionId) {
|
|
109
|
-
ws.send(JSON.stringify({
|
|
110
|
-
id: this.subscriptionId,
|
|
111
|
-
type: 'complete',
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
ws.close();
|
|
115
|
-
}
|
|
116
|
-
if (this.wsClient === ws)
|
|
117
|
-
this.wsClient = null;
|
|
118
|
-
this.wsUnsubscribe = null;
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
dispatch(notification) {
|
|
122
|
-
const type = notification.__typename;
|
|
123
|
-
if (!type) {
|
|
124
|
-
console.warn('Received notification without __typename:', notification);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
// Snapshot the subscribers list so handlers that mutate the registry
|
|
128
|
-
// (e.g. by calling close()) don't affect this dispatch loop.
|
|
129
|
-
for (const sub of [...this.subscribers.values()]) {
|
|
130
|
-
try {
|
|
131
|
-
switch (type) {
|
|
132
|
-
case 'ActorUpdateNotification':
|
|
133
|
-
sub.handlers.onActorUpdate?.(notification);
|
|
134
|
-
break;
|
|
135
|
-
case 'ActorUpdateResponse':
|
|
136
|
-
sub.handlers.onActorUpdateResponse?.(notification);
|
|
137
|
-
break;
|
|
138
|
-
case 'VoxelUpdateNotification':
|
|
139
|
-
sub.handlers.onVoxelUpdate?.(notification);
|
|
140
|
-
break;
|
|
141
|
-
case 'VoxelUpdateResponse':
|
|
142
|
-
sub.handlers.onVoxelUpdateResponse?.(notification);
|
|
143
|
-
break;
|
|
144
|
-
case 'ClientAudioNotification':
|
|
145
|
-
sub.handlers.onClientAudio?.(notification);
|
|
146
|
-
break;
|
|
147
|
-
case 'ClientTextNotification':
|
|
148
|
-
sub.handlers.onClientText?.(notification);
|
|
149
|
-
break;
|
|
150
|
-
case 'ClientEventNotification':
|
|
151
|
-
sub.handlers.onClientEvent?.(notification);
|
|
152
|
-
break;
|
|
153
|
-
case 'ServerEventNotification':
|
|
154
|
-
sub.handlers.onServerEvent?.(notification);
|
|
155
|
-
break;
|
|
156
|
-
case 'GenericErrorResponse':
|
|
157
|
-
sub.handlers.onGenericError?.(notification);
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
console.error(`Handler for ${type} threw:`, error);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
checkIfShouldUnsubscribe() {
|
|
167
|
-
if (this.subscribers.size === 0 && this.wsUnsubscribe) {
|
|
168
|
-
this.wsUnsubscribe();
|
|
169
|
-
this.wsUnsubscribe = null;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
close() {
|
|
173
|
-
this.subscribers.clear();
|
|
174
|
-
if (this.wsUnsubscribe) {
|
|
175
|
-
this.wsUnsubscribe();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const UDP_NOTIFICATIONS_QUERY = `subscription {
|
|
180
|
-
udpNotifications {
|
|
181
|
-
__typename
|
|
182
|
-
... on ActorUpdateNotification { appId chunkX chunkY chunkZ distance decayRate uuid state sequenceNumber epochMillis }
|
|
183
|
-
... on VoxelUpdateNotification { appId chunkX chunkY chunkZ distance decayRate uuid voxelX voxelY voxelZ voxelType voxelState sequenceNumber epochMillis }
|
|
184
|
-
... on GenericErrorResponse { sequenceNumber errorCode }
|
|
185
|
-
... on ActorUpdateResponse { appId chunkX chunkY chunkZ distance decayRate uuid sequenceNumber epochMillis }
|
|
186
|
-
... on VoxelUpdateResponse { appId chunkX chunkY chunkZ distance decayRate uuid sequenceNumber epochMillis }
|
|
187
|
-
... on ClientAudioNotification { appId chunkX chunkY chunkZ distance decayRate uuid audioData sequenceNumber epochMillis }
|
|
188
|
-
... on ClientTextNotification { appId chunkX chunkY chunkZ distance decayRate uuid text sequenceNumber epochMillis }
|
|
189
|
-
... on ClientEventNotification { appId chunkX chunkY chunkZ distance decayRate uuid eventType state sequenceNumber epochMillis }
|
|
190
|
-
... on ServerEventNotification { appId chunkX chunkY chunkZ distance decayRate uuid eventType state sequenceNumber epochMillis }
|
|
191
|
-
}
|
|
192
|
-
}`;
|
|
1
|
+
export { RealtimeClient as SubscriptionManager, } from './realtime.js';
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChunkCoordinatesInput } from './generated/graphql.js';
|
|
2
|
+
export declare class SequenceAllocator {
|
|
3
|
+
private nextValue;
|
|
4
|
+
constructor(seed?: number);
|
|
5
|
+
next(): number;
|
|
6
|
+
}
|
|
7
|
+
export declare function generateCrowdyUuid(): string;
|
|
8
|
+
export declare function validateCrowdyUuid(uuid: string): void;
|
|
9
|
+
export declare function encodeBase64(bytes: Uint8Array): string;
|
|
10
|
+
export declare function decodeBase64(value: string): Uint8Array;
|
|
11
|
+
export declare function validateChunkCoordinates(chunk: ChunkCoordinatesInput): void;
|
|
12
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAW,MAAM,wBAAwB,CAAC;AAE7E,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,SAAI;IAIpB,IAAI,IAAI,MAAM;CAKf;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMrD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAOtD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAS3E"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CrowdyProtocolError } from './errors.js';
|
|
2
|
+
export class SequenceAllocator {
|
|
3
|
+
constructor(seed = 1) {
|
|
4
|
+
this.nextValue = seed & 0xff;
|
|
5
|
+
}
|
|
6
|
+
next() {
|
|
7
|
+
const value = this.nextValue;
|
|
8
|
+
this.nextValue = (this.nextValue + 1) & 0xff;
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function generateCrowdyUuid() {
|
|
13
|
+
const bytes = new Uint8Array(16);
|
|
14
|
+
crypto.getRandomValues(bytes);
|
|
15
|
+
return Array.from(bytes)
|
|
16
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
17
|
+
.join('');
|
|
18
|
+
}
|
|
19
|
+
export function validateCrowdyUuid(uuid) {
|
|
20
|
+
if (new TextEncoder().encode(uuid).length !== 32) {
|
|
21
|
+
throw new CrowdyProtocolError({
|
|
22
|
+
message: 'Crowdy UUID must be exactly 32 bytes when UTF-8 encoded',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function encodeBase64(bytes) {
|
|
27
|
+
let binary = '';
|
|
28
|
+
for (const byte of bytes) {
|
|
29
|
+
binary += String.fromCharCode(byte);
|
|
30
|
+
}
|
|
31
|
+
return btoa(binary);
|
|
32
|
+
}
|
|
33
|
+
export function decodeBase64(value) {
|
|
34
|
+
const binary = atob(value);
|
|
35
|
+
const bytes = new Uint8Array(binary.length);
|
|
36
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
37
|
+
bytes[index] = binary.charCodeAt(index);
|
|
38
|
+
}
|
|
39
|
+
return bytes;
|
|
40
|
+
}
|
|
41
|
+
export function validateChunkCoordinates(chunk) {
|
|
42
|
+
for (const axis of ['x', 'y', 'z']) {
|
|
43
|
+
const value = BigInt(chunk[axis]);
|
|
44
|
+
if (value < -9223372036854775808n || value > 9223372036854775807n) {
|
|
45
|
+
throw new CrowdyProtocolError({
|
|
46
|
+
message: `Chunk coordinate ${axis} is outside signed int64 range`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/world.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { UdpAPI } from './domains/udp.js';
|
|
2
|
+
import type { ActorUpdateRequestInput, ChunkCoordinatesInput, ClientEventNotificationInput, ClientTextPacketInput, Scalars, VoxelUpdateRequestInput } from './generated/graphql.js';
|
|
3
|
+
import type { SpatialNotification, UdpNotificationHandlers } from './realtime.js';
|
|
4
|
+
export interface ActorOptions {
|
|
5
|
+
uuid?: string;
|
|
6
|
+
defaultDistance?: number;
|
|
7
|
+
defaultDecayRate?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class WorldClient {
|
|
10
|
+
private readonly appId;
|
|
11
|
+
private readonly udp;
|
|
12
|
+
constructor(appId: Scalars['BigInt']['input'], udp: UdpAPI);
|
|
13
|
+
actor(options?: ActorOptions): ActorClient;
|
|
14
|
+
subscribe(handlers: UdpNotificationHandlers): () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare class ActorClient {
|
|
17
|
+
private readonly appId;
|
|
18
|
+
private readonly udp;
|
|
19
|
+
private readonly options;
|
|
20
|
+
readonly uuid: string;
|
|
21
|
+
private chunk;
|
|
22
|
+
constructor(appId: Scalars['BigInt']['input'], udp: UdpAPI, options: Required<Pick<ActorOptions, 'uuid'>> & Pick<ActorOptions, 'defaultDistance' | 'defaultDecayRate'>);
|
|
23
|
+
join(chunk: ChunkCoordinatesInput, state?: string): Promise<SpatialNotification>;
|
|
24
|
+
sendState(state: string, options?: Partial<Pick<ActorUpdateRequestInput, 'chunk' | 'distance' | 'decayRate'>>): Promise<SpatialNotification>;
|
|
25
|
+
sendVoxelUpdate(input: Omit<VoxelUpdateRequestInput, 'appId' | 'uuid' | 'chunk'> & {
|
|
26
|
+
chunk?: ChunkCoordinatesInput;
|
|
27
|
+
}): Promise<SpatialNotification>;
|
|
28
|
+
sendText(text: string, input?: Partial<Omit<ClientTextPacketInput, 'appId' | 'uuid' | 'text' | 'chunk'>> & {
|
|
29
|
+
chunk?: ChunkCoordinatesInput;
|
|
30
|
+
}): Promise<SpatialNotification>;
|
|
31
|
+
sendEvent(eventType: number, state: string, input?: Partial<Omit<ClientEventNotificationInput, 'appId' | 'uuid' | 'eventType' | 'state' | 'chunk'>> & {
|
|
32
|
+
chunk?: ChunkCoordinatesInput;
|
|
33
|
+
}): Promise<SpatialNotification>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=world.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../src/world.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,OAAO,EACP,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGlF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,GAAG,EAAE,MAAM;IAG9B,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,WAAW;IAQ9C,SAAS,CAAC,QAAQ,EAAE,uBAAuB,GAAG,MAAM,IAAI;CAGzD;AAED,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,KAAK,CAAsC;gBAGhC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,GAC5D,IAAI,CAAC,YAAY,EAAE,iBAAiB,GAAG,kBAAkB,CAAC;IAMxD,IAAI,CACR,KAAK,EAAE,qBAAqB,EAC5B,KAAK,SAAS,GACb,OAAO,CAAC,mBAAmB,CAAC;IAKzB,SAAS,CACb,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC,CAAM,GACvF,OAAO,CAAC,mBAAmB,CAAC;IAgBzB,eAAe,CACnB,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG;QACjE,KAAK,CAAC,EAAE,qBAAqB,CAAC;KAC/B,GACA,OAAO,CAAC,mBAAmB,CAAC;IAczB,QAAQ,CACZ,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG;QACjF,KAAK,CAAC,EAAE,qBAAqB,CAAC;KAC1B,GACL,OAAO,CAAC,mBAAmB,CAAC;IAezB,SAAS,CACb,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,OAAO,CAAC,IAAI,CAAC,4BAA4B,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC,GAAG;QACvG,KAAK,CAAC,EAAE,qBAAqB,CAAC;KAC1B,GACL,OAAO,CAAC,mBAAmB,CAAC;CAehC"}
|
package/dist/world.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { generateCrowdyUuid, validateChunkCoordinates, validateCrowdyUuid } from './utils.js';
|
|
2
|
+
export class WorldClient {
|
|
3
|
+
constructor(appId, udp) {
|
|
4
|
+
this.appId = appId;
|
|
5
|
+
this.udp = udp;
|
|
6
|
+
}
|
|
7
|
+
actor(options = {}) {
|
|
8
|
+
return new ActorClient(this.appId, this.udp, {
|
|
9
|
+
uuid: options.uuid ?? generateCrowdyUuid(),
|
|
10
|
+
defaultDistance: options.defaultDistance,
|
|
11
|
+
defaultDecayRate: options.defaultDecayRate,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
subscribe(handlers) {
|
|
15
|
+
return this.udp.subscribe(handlers);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class ActorClient {
|
|
19
|
+
constructor(appId, udp, options) {
|
|
20
|
+
this.appId = appId;
|
|
21
|
+
this.udp = udp;
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.chunk = null;
|
|
24
|
+
validateCrowdyUuid(options.uuid);
|
|
25
|
+
this.uuid = options.uuid;
|
|
26
|
+
}
|
|
27
|
+
async join(chunk, state = 'AA==') {
|
|
28
|
+
this.chunk = chunk;
|
|
29
|
+
return this.sendState(state, { chunk });
|
|
30
|
+
}
|
|
31
|
+
async sendState(state, options = {}) {
|
|
32
|
+
const chunk = options.chunk ?? this.chunk;
|
|
33
|
+
if (!chunk) {
|
|
34
|
+
throw new Error('Actor must join a chunk before sending state');
|
|
35
|
+
}
|
|
36
|
+
validateChunkCoordinates(chunk);
|
|
37
|
+
return this.udp.sendActorUpdateAndWait({
|
|
38
|
+
appId: this.appId,
|
|
39
|
+
chunk,
|
|
40
|
+
uuid: this.uuid,
|
|
41
|
+
state,
|
|
42
|
+
distance: options.distance ?? this.options.defaultDistance,
|
|
43
|
+
decayRate: options.decayRate ?? this.options.defaultDecayRate,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async sendVoxelUpdate(input) {
|
|
47
|
+
const chunk = input.chunk ?? this.chunk;
|
|
48
|
+
if (!chunk) {
|
|
49
|
+
throw new Error('Actor must join a chunk before sending voxel updates');
|
|
50
|
+
}
|
|
51
|
+
validateChunkCoordinates(chunk);
|
|
52
|
+
return this.udp.sendVoxelUpdateAndWait({
|
|
53
|
+
...input,
|
|
54
|
+
appId: this.appId,
|
|
55
|
+
chunk,
|
|
56
|
+
uuid: this.uuid,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async sendText(text, input = {}) {
|
|
60
|
+
const chunk = input.chunk ?? this.chunk;
|
|
61
|
+
if (!chunk) {
|
|
62
|
+
throw new Error('Actor must join a chunk before sending text');
|
|
63
|
+
}
|
|
64
|
+
validateChunkCoordinates(chunk);
|
|
65
|
+
return this.udp.sendTextPacketAndWait({
|
|
66
|
+
...input,
|
|
67
|
+
appId: this.appId,
|
|
68
|
+
chunk,
|
|
69
|
+
uuid: this.uuid,
|
|
70
|
+
text,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async sendEvent(eventType, state, input = {}) {
|
|
74
|
+
const chunk = input.chunk ?? this.chunk;
|
|
75
|
+
if (!chunk) {
|
|
76
|
+
throw new Error('Actor must join a chunk before sending events');
|
|
77
|
+
}
|
|
78
|
+
validateChunkCoordinates(chunk);
|
|
79
|
+
return this.udp.sendClientEventAndWait({
|
|
80
|
+
...input,
|
|
81
|
+
appId: this.appId,
|
|
82
|
+
chunk,
|
|
83
|
+
uuid: this.uuid,
|
|
84
|
+
eventType,
|
|
85
|
+
state,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowdedkingdomstudios/crowdyjs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Client SDK for Crowded Kingdoms GraphQL API with UDP proxy support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,17 +9,26 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./generated": {
|
|
14
|
+
"import": "./dist/generated/graphql.js",
|
|
15
|
+
"types": "./dist/generated/graphql.d.ts"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
19
|
"dist",
|
|
16
|
-
"README.md"
|
|
20
|
+
"README.md",
|
|
21
|
+
"MIGRATION.md"
|
|
17
22
|
],
|
|
18
23
|
"scripts": {
|
|
24
|
+
"sync-schema": "node scripts/sync-schema.mjs",
|
|
25
|
+
"precodegen": "npm run sync-schema",
|
|
19
26
|
"codegen": "graphql-codegen --config codegen.ts",
|
|
27
|
+
"check:schema": "npm run codegen && git diff --exit-code schema.gql src/generated/graphql.ts",
|
|
20
28
|
"codegen:watch": "graphql-codegen --config codegen.ts --watch",
|
|
21
29
|
"prebuild": "npm run codegen",
|
|
22
30
|
"build": "tsc",
|
|
31
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
23
32
|
"watch": "tsc --watch",
|
|
24
33
|
"clean": "rm -rf dist",
|
|
25
34
|
"prepublishOnly": "npm run clean && npm run build"
|
|
@@ -55,6 +64,7 @@
|
|
|
55
64
|
},
|
|
56
65
|
"dependencies": {
|
|
57
66
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
58
|
-
"graphql": "^16.13.2"
|
|
67
|
+
"graphql": "^16.13.2",
|
|
68
|
+
"graphql-ws": "^6.0.8"
|
|
59
69
|
}
|
|
60
70
|
}
|