modd-network 1.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/README.md +41 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +177 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# MODD Network SDK
|
|
2
|
+
|
|
3
|
+
The official SDK for connecting to the MODD mesh network for multiplayer applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install modd-network
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { MODD } from 'modd-network';
|
|
15
|
+
|
|
16
|
+
// Initialize the client
|
|
17
|
+
const client = new MODD('your-app-id', {
|
|
18
|
+
centralServiceUrl: 'ws://your-central-service.com', // Optional
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Create a room
|
|
22
|
+
await client.createRoom('my-room-id', { level: 1, score: 0 });
|
|
23
|
+
|
|
24
|
+
// Or join a room
|
|
25
|
+
await client.joinRoom('my-room-id');
|
|
26
|
+
|
|
27
|
+
// Listen for inputs
|
|
28
|
+
client.onInput((input) => {
|
|
29
|
+
console.log('Received input:', input);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Send input
|
|
33
|
+
client.sendInput({ x: 10, y: 20 });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Room creation and joining
|
|
39
|
+
- Real-time input synchronization with sequencing
|
|
40
|
+
- Snapshot state management
|
|
41
|
+
- Mesh network connectivity
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface MODDOptions {
|
|
2
|
+
centralServiceUrl?: string;
|
|
3
|
+
nodeUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OrderedInput {
|
|
6
|
+
data: any;
|
|
7
|
+
sequenceNumber: number;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
clientId: string;
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
export type InputHandler = (input: OrderedInput) => void;
|
|
13
|
+
export type SnapshotHandler = (snapshot: any) => void;
|
|
14
|
+
export declare class MODD {
|
|
15
|
+
private ws;
|
|
16
|
+
private roomId;
|
|
17
|
+
private appId;
|
|
18
|
+
private centralServiceUrl;
|
|
19
|
+
private nodeUrl;
|
|
20
|
+
private snapshot;
|
|
21
|
+
private inputHandlers;
|
|
22
|
+
private snapshotHandlers;
|
|
23
|
+
constructor(appId: string, options?: MODDOptions);
|
|
24
|
+
createRoom(roomId: string, initialSnapshot: any): Promise<void>;
|
|
25
|
+
joinRoom(roomId: string): Promise<void>;
|
|
26
|
+
private setupMessageHandlers;
|
|
27
|
+
sendInput(data: any): void;
|
|
28
|
+
sendSnapshot(snapshot: any): void;
|
|
29
|
+
onInput(handler: InputHandler): void;
|
|
30
|
+
onSnapshot(handler: SnapshotHandler): void;
|
|
31
|
+
getSnapshot(): any;
|
|
32
|
+
disconnect(): void;
|
|
33
|
+
private getNodeUrl;
|
|
34
|
+
private hashSnapshot;
|
|
35
|
+
}
|
|
36
|
+
export declare class MeshClient extends MODD {
|
|
37
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MeshClient = exports.MODD = void 0;
|
|
7
|
+
const ws_1 = __importDefault(require("ws"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
// Primary SDK client: MODD
|
|
10
|
+
class MODD {
|
|
11
|
+
ws = null;
|
|
12
|
+
roomId = null;
|
|
13
|
+
appId = null;
|
|
14
|
+
centralServiceUrl;
|
|
15
|
+
nodeUrl = null;
|
|
16
|
+
snapshot = null;
|
|
17
|
+
inputHandlers = [];
|
|
18
|
+
snapshotHandlers = [];
|
|
19
|
+
constructor(appId, options = {}) {
|
|
20
|
+
this.appId = appId;
|
|
21
|
+
this.centralServiceUrl = options.centralServiceUrl || 'ws://localhost:9001';
|
|
22
|
+
this.nodeUrl = options.nodeUrl || null;
|
|
23
|
+
}
|
|
24
|
+
async createRoom(roomId, initialSnapshot) {
|
|
25
|
+
if (!this.appId) {
|
|
26
|
+
throw new Error('appId is required. Initialize MODD with appId in constructor.');
|
|
27
|
+
}
|
|
28
|
+
this.roomId = roomId;
|
|
29
|
+
this.snapshot = initialSnapshot;
|
|
30
|
+
const nodeUrl = this.nodeUrl || await this.getNodeUrl(roomId);
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
this.ws = new ws_1.default(nodeUrl);
|
|
33
|
+
this.ws.on('open', () => {
|
|
34
|
+
this.ws.send(JSON.stringify({
|
|
35
|
+
type: 'CREATE_ROOM',
|
|
36
|
+
payload: { roomId, appId: this.appId, snapshot: initialSnapshot }
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
this.ws.on('message', (data) => {
|
|
40
|
+
const message = JSON.parse(data.toString());
|
|
41
|
+
if (message.type === 'ROOM_CREATED') {
|
|
42
|
+
console.log('Room created:', roomId);
|
|
43
|
+
this.setupMessageHandlers();
|
|
44
|
+
resolve();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.ws.on('error', reject);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async joinRoom(roomId) {
|
|
51
|
+
if (!this.appId) {
|
|
52
|
+
throw new Error('appId is required. Initialize MODD with appId in constructor.');
|
|
53
|
+
}
|
|
54
|
+
this.roomId = roomId;
|
|
55
|
+
const nodeUrl = this.nodeUrl || await this.getNodeUrl(roomId);
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
this.ws = new ws_1.default(nodeUrl);
|
|
58
|
+
this.ws.on('open', () => {
|
|
59
|
+
this.ws.send(JSON.stringify({
|
|
60
|
+
type: 'JOIN_ROOM',
|
|
61
|
+
payload: { roomId, appId: this.appId }
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
this.ws.on('message', (data) => {
|
|
65
|
+
const message = JSON.parse(data.toString());
|
|
66
|
+
if (message.type === 'INITIAL_STATE') {
|
|
67
|
+
this.snapshot = message.payload.snapshot;
|
|
68
|
+
const inputs = message.payload.inputs || [];
|
|
69
|
+
// Apply any pending inputs to catch up (in sequence order)
|
|
70
|
+
const sortedInputs = [...inputs].sort((a, b) => a.sequenceNumber - b.sequenceNumber);
|
|
71
|
+
sortedInputs.forEach((input) => {
|
|
72
|
+
const orderedInput = {
|
|
73
|
+
data: input.data,
|
|
74
|
+
sequenceNumber: input.sequenceNumber,
|
|
75
|
+
timestamp: input.timestamp,
|
|
76
|
+
clientId: input.clientId,
|
|
77
|
+
id: input.id
|
|
78
|
+
};
|
|
79
|
+
this.inputHandlers.forEach(handler => handler(orderedInput));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (message.type === 'ROOM_JOINED') {
|
|
83
|
+
console.log('Joined room:', roomId);
|
|
84
|
+
this.setupMessageHandlers();
|
|
85
|
+
resolve();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
this.ws.on('error', reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
setupMessageHandlers() {
|
|
92
|
+
if (!this.ws)
|
|
93
|
+
return;
|
|
94
|
+
this.ws.on('message', (data) => {
|
|
95
|
+
const message = JSON.parse(data.toString());
|
|
96
|
+
if (message.type === 'ORDERED_INPUTS') {
|
|
97
|
+
const inputs = message.payload.inputs || [];
|
|
98
|
+
inputs.forEach((input) => {
|
|
99
|
+
const orderedInput = {
|
|
100
|
+
data: input.data,
|
|
101
|
+
sequenceNumber: input.sequenceNumber,
|
|
102
|
+
timestamp: input.timestamp,
|
|
103
|
+
clientId: input.clientId,
|
|
104
|
+
id: input.id
|
|
105
|
+
};
|
|
106
|
+
this.inputHandlers.forEach(handler => handler(orderedInput));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (message.type === 'SNAPSHOT_UPDATE') {
|
|
110
|
+
this.snapshot = message.payload.snapshot;
|
|
111
|
+
this.snapshotHandlers.forEach(handler => handler(this.snapshot));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
sendInput(data) {
|
|
116
|
+
if (!this.ws || !this.roomId) {
|
|
117
|
+
throw new Error('Not connected to a room');
|
|
118
|
+
}
|
|
119
|
+
this.ws.send(JSON.stringify({
|
|
120
|
+
type: 'SEND_INPUT',
|
|
121
|
+
payload: { roomId: this.roomId, data }
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
sendSnapshot(snapshot) {
|
|
125
|
+
if (!this.ws || !this.roomId) {
|
|
126
|
+
throw new Error('Not connected to a room');
|
|
127
|
+
}
|
|
128
|
+
this.snapshot = snapshot;
|
|
129
|
+
const hash = this.hashSnapshot(snapshot);
|
|
130
|
+
this.ws.send(JSON.stringify({
|
|
131
|
+
type: 'SEND_SNAPSHOT',
|
|
132
|
+
payload: { roomId: this.roomId, snapshot, hash }
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
onInput(handler) {
|
|
136
|
+
this.inputHandlers.push(handler);
|
|
137
|
+
}
|
|
138
|
+
onSnapshot(handler) {
|
|
139
|
+
this.snapshotHandlers.push(handler);
|
|
140
|
+
}
|
|
141
|
+
getSnapshot() {
|
|
142
|
+
return this.snapshot;
|
|
143
|
+
}
|
|
144
|
+
disconnect() {
|
|
145
|
+
if (this.ws) {
|
|
146
|
+
this.ws.close();
|
|
147
|
+
this.ws = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async getNodeUrl(roomId) {
|
|
151
|
+
if (!this.appId) {
|
|
152
|
+
throw new Error('appId is required');
|
|
153
|
+
}
|
|
154
|
+
// Use REST API instead of WebSocket for better error handling
|
|
155
|
+
const httpUrl = this.centralServiceUrl.replace('ws://', 'http://').replace('wss://', 'https://');
|
|
156
|
+
const response = await fetch(`${httpUrl}/api/apps/${this.appId}/rooms/${roomId}/connect`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: { 'Content-Type': 'application/json' },
|
|
159
|
+
body: JSON.stringify({})
|
|
160
|
+
});
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
const error = await response.json().catch(() => ({ error: 'Failed to connect to room' }));
|
|
163
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
164
|
+
}
|
|
165
|
+
const data = await response.json();
|
|
166
|
+
return data.url;
|
|
167
|
+
}
|
|
168
|
+
hashSnapshot(snapshot) {
|
|
169
|
+
const data = JSON.stringify(snapshot);
|
|
170
|
+
return crypto_1.default.createHash('sha256').update(data).digest('hex');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.MODD = MODD;
|
|
174
|
+
// Backwards-compatible alias
|
|
175
|
+
class MeshClient extends MODD {
|
|
176
|
+
}
|
|
177
|
+
exports.MeshClient = MeshClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "modd-network",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SDK for connecting to mesh network for multiplayer applications",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsx watch src/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mesh",
|
|
19
|
+
"network",
|
|
20
|
+
"multiplayer",
|
|
21
|
+
"sdk"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"ws": "^8.16.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.10.6",
|
|
30
|
+
"@types/ws": "^8.5.10",
|
|
31
|
+
"tsx": "^4.7.0",
|
|
32
|
+
"typescript": "^5.3.3"
|
|
33
|
+
}
|
|
34
|
+
}
|