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 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
@@ -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
+ }