New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@mml-io/3d-web-user-networking

Package Overview
Dependencies
Maintainers
0
Versions
173
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mml-io/3d-web-user-networking - npm Package Compare versions

Comparing version 0.0.0-experimental-4ca92b1-20231017 to 0.0.0-experimental-4ec4338-20250205

build/UserData.d.ts

3

build/index.d.ts
export * from "./UserNetworkingCodec";
export * from "./UserNetworkingServer";
export * from "./UserNetworkingClient";
export * from "./UserData";
export * from "./ReconnectingWebSocket";
export * from "./messages";
export * from "./UserNetworkingMessages";

@@ -30,9 +30,2 @@ // src/UserNetworkingCodec.ts

// src/messages.ts
var CONNECTED_MESSAGE_TYPE = "connected";
var DISCONNECTED_MESSAGE_TYPE = "disconnected";
var IDENTITY_MESSAGE_TYPE = "identity";
var PING_MESSAGE_TYPE = "ping";
var PONG_MESSAGE_TYPE = "pong";
// src/user-networking-settings.ts

@@ -43,27 +36,34 @@ var pingPongRate = 1500;

// src/UserNetworkingMessages.ts
var USER_NETWORKING_DISCONNECTED_MESSAGE_TYPE = "disconnected";
var USER_NETWORKING_IDENTITY_MESSAGE_TYPE = "identity";
var USER_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
var USER_NETWORKING_USER_PROFILE_MESSAGE_TYPE = "user_profile";
var USER_NETWORKING_USER_UPDATE_MESSAGE_TYPE = "user_update";
var USER_NETWORKING_SERVER_BROADCAST_MESSAGE_TYPE = "broadcast";
var USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE = "error";
var USER_NETWORKING_PING_MESSAGE_TYPE = "ping";
var USER_NETWORKING_PONG_MESSAGE_TYPE = "pong";
var USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE = "CONNECTION_LIMIT_REACHED";
var USER_NETWORKING_AUTHENTICATION_FAILED_ERROR_TYPE = "AUTHENTICATION_FAILED";
var USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE = "SERVER_SHUTDOWN";
var USER_NETWORKING_UNKNOWN_ERROR = "UNKNOWN_ERROR";
// src/UserNetworkingServer.ts
var WebSocketOpenStatus = 1;
var UserNetworkingServer = class {
constructor() {
this.clients = /* @__PURE__ */ new Map();
this.clientLastPong = /* @__PURE__ */ new Map();
setInterval(this.sendUpdates.bind(this), packetsUpdateRate);
setInterval(this.pingClients.bind(this), pingPongRate);
setInterval(this.heartBeat.bind(this), heartBeatRate);
constructor(options) {
this.options = options;
this.allClientsById = /* @__PURE__ */ new Map();
this.authenticatedClientsById = /* @__PURE__ */ new Map();
this.sendUpdatesIntervalTimer = setInterval(this.sendUpdates.bind(this), packetsUpdateRate);
this.pingClientsIntervalTimer = setInterval(this.pingClients.bind(this), pingPongRate);
this.heartbeatIntervalTimer = setInterval(this.heartBeat.bind(this), heartBeatRate);
}
heartBeat() {
const now = Date.now();
this.clientLastPong.forEach((clientLastPong, id) => {
if (now - clientLastPong > heartBeatRate) {
this.clients.delete(id);
this.clientLastPong.delete(id);
const disconnectMessage = JSON.stringify({
id,
type: DISCONNECTED_MESSAGE_TYPE
});
for (const { socket: otherSocket } of this.clients.values()) {
if (otherSocket.readyState === WebSocketOpenStatus) {
otherSocket.send(disconnectMessage);
}
}
this.allClientsById.forEach((client) => {
if (now - client.lastPong > heartBeatRate) {
client.socket.close();
this.handleDisconnectedClient(client);
}

@@ -73,5 +73,7 @@ });

pingClients() {
this.clients.forEach((client) => {
const message = { type: "ping" };
const messageString = JSON.stringify(message);
this.authenticatedClientsById.forEach((client) => {
if (client.socket.readyState === WebSocketOpenStatus) {
client.socket.send(JSON.stringify({ type: "ping" }));
client.socket.send(messageString);
}

@@ -82,28 +84,28 @@ });

let id = 1;
while (this.clients.has(id))
while (this.allClientsById.has(id)) {
id++;
}
return id;
}
broadcastMessage(broadcastType, broadcastPayload) {
const message = {
type: "broadcast",
broadcastType,
payload: broadcastPayload
};
const messageString = JSON.stringify(message);
for (const [, client] of this.authenticatedClientsById) {
if (client.socket.readyState === WebSocketOpenStatus) {
client.socket.send(messageString);
}
}
}
connectClient(socket) {
const id = this.getId();
console.log(`Client ID: ${id} joined`);
const connectMessage = JSON.stringify({
console.log(`Client ID: ${id} joined, waiting for user-identification`);
const client = {
id,
type: CONNECTED_MESSAGE_TYPE
});
for (const { socket: otherSocket } of this.clients.values()) {
if (otherSocket.readyState === WebSocketOpenStatus) {
otherSocket.send(connectMessage);
}
}
const identityMessage = JSON.stringify({
id,
type: IDENTITY_MESSAGE_TYPE
});
socket.send(identityMessage);
for (const { update } of this.clients.values()) {
socket.send(UserNetworkingCodec.encodeUpdate(update));
}
this.clients.set(id, {
lastPong: Date.now(),
socket,
authenticatedUser: null,
update: {

@@ -115,3 +117,4 @@ id,

}
});
};
this.allClientsById.set(id, client);
socket.on("message", (message, _isBinary) => {

@@ -122,14 +125,87 @@ if (message instanceof Buffer) {

update.id = id;
if (this.clients.get(id) !== void 0) {
this.clients.get(id).update = update;
}
client.update = update;
} else {
let parsed;
try {
const data = JSON.parse(message);
if (data.type === "pong") {
this.clientLastPong.set(id, Date.now());
}
parsed = JSON.parse(message);
} catch (e) {
console.error("Error parsing JSON message", message, e);
return;
}
if (!client.authenticatedUser) {
if (parsed.type === USER_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE) {
this.handleUserAuth(client, parsed).then((authResult) => {
var _a, _b;
if (client.socket.readyState !== WebSocketOpenStatus) {
return;
}
if (!authResult) {
const serverError = JSON.stringify({
type: USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
errorType: USER_NETWORKING_AUTHENTICATION_FAILED_ERROR_TYPE,
message: "Authentication failed"
});
socket.send(serverError);
socket.close();
} else {
if (this.options.connectionLimit !== void 0 && this.authenticatedClientsById.size >= this.options.connectionLimit) {
const serverError = JSON.stringify({
type: USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
errorType: USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE,
message: "Connection limit reached"
});
socket.send(serverError);
socket.close();
return;
}
const userData = authResult;
client.authenticatedUser = userData;
const userProfileMessage = JSON.stringify({
id: client.id,
type: USER_NETWORKING_USER_PROFILE_MESSAGE_TYPE,
username: userData.username,
characterDescription: userData.characterDescription
});
client.socket.send(userProfileMessage);
const identityMessage = JSON.stringify({
id: client.id,
type: USER_NETWORKING_IDENTITY_MESSAGE_TYPE
});
client.socket.send(identityMessage);
const userUpdateMessage = UserNetworkingCodec.encodeUpdate(client.update);
for (const [, otherClient] of this.authenticatedClientsById) {
if (otherClient.socket.readyState !== WebSocketOpenStatus || otherClient === client) {
continue;
}
client.socket.send(
JSON.stringify({
id: otherClient.update.id,
type: USER_NETWORKING_USER_PROFILE_MESSAGE_TYPE,
username: (_a = otherClient.authenticatedUser) == null ? void 0 : _a.username,
characterDescription: (_b = otherClient.authenticatedUser) == null ? void 0 : _b.characterDescription
})
);
client.socket.send(UserNetworkingCodec.encodeUpdate(otherClient.update));
otherClient.socket.send(userProfileMessage);
otherClient.socket.send(userUpdateMessage);
}
this.authenticatedClientsById.set(id, client);
}
});
} else {
console.error(`Unhandled message pre-auth: ${JSON.stringify(parsed)}`);
socket.close();
}
} else {
switch (parsed.type) {
case USER_NETWORKING_PONG_MESSAGE_TYPE:
client.lastPong = Date.now();
break;
case USER_NETWORKING_USER_UPDATE_MESSAGE_TYPE:
this.handleUserUpdate(id, parsed);
break;
default:
console.error(`Unhandled message: ${JSON.stringify(parsed)}`);
}
}
}

@@ -139,19 +215,89 @@ });

console.log("Client disconnected", id);
this.clients.delete(id);
this.handleDisconnectedClient(client);
});
}
handleDisconnectedClient(client) {
if (!this.allClientsById.has(client.id)) {
return;
}
this.allClientsById.delete(client.id);
if (client.authenticatedUser !== null) {
this.options.onClientDisconnect(client.id);
this.authenticatedClientsById.delete(client.id);
const disconnectMessage = JSON.stringify({
id,
type: DISCONNECTED_MESSAGE_TYPE
id: client.id,
type: USER_NETWORKING_DISCONNECTED_MESSAGE_TYPE
});
for (const [clientId, { socket: otherSocket }] of this.clients) {
if (otherSocket.readyState === WebSocketOpenStatus) {
otherSocket.send(disconnectMessage);
for (const [, otherClient] of this.authenticatedClientsById) {
if (otherClient.socket.readyState === WebSocketOpenStatus) {
otherClient.socket.send(disconnectMessage);
}
}
}
}
async handleUserAuth(client, credentials) {
const userData = this.options.onClientConnect(
client.id,
credentials.sessionToken,
credentials.userIdentity
);
let resolvedUserData;
if (userData instanceof Promise) {
resolvedUserData = await userData;
} else {
resolvedUserData = userData;
}
if (resolvedUserData === null) {
console.error(`Client-id ${client.id} user_auth unauthorized and ignored`);
return false;
}
console.log("Client authenticated", client.id, resolvedUserData);
return resolvedUserData;
}
updateUserCharacter(clientId, userData) {
this.internalUpdateUser(clientId, userData);
}
internalUpdateUser(clientId, userData) {
const client = this.authenticatedClientsById.get(clientId);
client.authenticatedUser = userData;
this.authenticatedClientsById.set(clientId, client);
const newUserData = JSON.stringify({
id: clientId,
type: USER_NETWORKING_USER_PROFILE_MESSAGE_TYPE,
username: userData.username,
characterDescription: userData.characterDescription
});
for (const [otherClientId, otherClient] of this.authenticatedClientsById) {
if (otherClient.socket.readyState === WebSocketOpenStatus) {
otherClient.socket.send(newUserData);
}
}
}
async handleUserUpdate(clientId, message) {
const client = this.authenticatedClientsById.get(clientId);
if (!client) {
console.error(`Client-id ${clientId} user_update ignored, client not found`);
return;
}
const authorizedUserData = this.options.onClientUserIdentityUpdate(
clientId,
message.userIdentity
);
let resolvedAuthorizedUserData;
if (authorizedUserData instanceof Promise) {
resolvedAuthorizedUserData = await authorizedUserData;
} else {
resolvedAuthorizedUserData = authorizedUserData;
}
if (!resolvedAuthorizedUserData) {
console.warn(`Client-id ${clientId} user_update unauthorized and ignored`);
return;
}
this.internalUpdateUser(clientId, resolvedAuthorizedUserData);
}
sendUpdates() {
for (const [clientId, client] of this.clients) {
for (const [clientId, client] of this.authenticatedClientsById) {
const update = client.update;
const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);
for (const [otherClientId, otherClient] of this.clients) {
for (const [otherClientId, otherClient] of this.authenticatedClientsById) {
if (otherClientId !== clientId && otherClient.socket.readyState === WebSocketOpenStatus) {

@@ -163,11 +309,23 @@ otherClient.socket.send(encodedUpdate);

}
dispose(clientCloseError) {
clearInterval(this.sendUpdatesIntervalTimer);
clearInterval(this.pingClientsIntervalTimer);
clearInterval(this.heartbeatIntervalTimer);
const stringifiedError = clientCloseError ? JSON.stringify(clientCloseError) : void 0;
for (const [, client] of this.authenticatedClientsById) {
if (stringifiedError) {
client.socket.send(stringifiedError);
}
client.socket.close();
}
}
};
// src/ReconnectingWebSocket.ts
var WebsocketStatus = /* @__PURE__ */ ((WebsocketStatus3) => {
WebsocketStatus3[WebsocketStatus3["Connecting"] = 0] = "Connecting";
WebsocketStatus3[WebsocketStatus3["Connected"] = 1] = "Connected";
WebsocketStatus3[WebsocketStatus3["Reconnecting"] = 2] = "Reconnecting";
WebsocketStatus3[WebsocketStatus3["Disconnected"] = 3] = "Disconnected";
return WebsocketStatus3;
var WebsocketStatus = /* @__PURE__ */ ((WebsocketStatus2) => {
WebsocketStatus2[WebsocketStatus2["Connecting"] = 0] = "Connecting";
WebsocketStatus2[WebsocketStatus2["Connected"] = 1] = "Connected";
WebsocketStatus2[WebsocketStatus2["Reconnecting"] = 2] = "Reconnecting";
WebsocketStatus2[WebsocketStatus2["Disconnected"] = 3] = "Disconnected";
return WebsocketStatus2;
})(WebsocketStatus || {});

@@ -285,3 +443,3 @@ var startingBackoffTimeMilliseconds = 100;

}
console.log("NetworkedDOMWebsocket close", e);
console.log("ReconnectingWebSocket close", e);
onWebsocketClose();

@@ -294,3 +452,3 @@ });

}
console.error("NetworkedDOMWebsocket error", e);
console.error("ReconnectingWebSocket error", e);
onWebsocketClose();

@@ -317,6 +475,13 @@ });

var UserNetworkingClient = class extends ReconnectingWebSocket {
constructor(url, websocketFactory, statusUpdateCallback, setIdentityCallback, clientUpdate) {
super(url, websocketFactory, statusUpdateCallback);
this.setIdentityCallback = setIdentityCallback;
this.clientUpdate = clientUpdate;
constructor(config) {
super(config.url, config.websocketFactory, (status) => {
if (status === 1 /* Connected */) {
this.sendMessage({
type: USER_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE,
sessionToken: config.sessionToken
});
}
config.statusUpdateCallback(status);
});
this.config = config;
}

@@ -327,2 +492,5 @@ sendUpdate(update) {

}
sendMessage(message) {
this.send(message);
}
handleIncomingWebsocketMessage(message) {

@@ -332,23 +500,39 @@ if (typeof message.data === "string") {

switch (parsed.type) {
case IDENTITY_MESSAGE_TYPE:
console.log(`Assigned ID: ${parsed.id}`);
this.setIdentityCallback(parsed.id);
case USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE:
console.error(`Server error: ${parsed.message}. errorType: ${parsed.errorType}`);
this.config.onServerError(parsed);
break;
case CONNECTED_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} joined`);
break;
case DISCONNECTED_MESSAGE_TYPE:
case USER_NETWORKING_DISCONNECTED_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} left`);
this.clientUpdate(parsed.id, null);
this.config.clientUpdate(parsed.id, null);
break;
case PING_MESSAGE_TYPE: {
this.send({ type: "pong" });
case USER_NETWORKING_IDENTITY_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} assigned to self`);
this.config.assignedIdentity(parsed.id);
break;
case USER_NETWORKING_USER_PROFILE_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} updated profile`);
this.config.clientProfileUpdated(parsed.id, parsed.username, parsed.characterDescription);
break;
case USER_NETWORKING_PING_MESSAGE_TYPE: {
this.sendMessage({ type: "pong" });
break;
}
case USER_NETWORKING_SERVER_BROADCAST_MESSAGE_TYPE: {
if (this.config.onServerBroadcast) {
this.config.onServerBroadcast({
broadcastType: parsed.broadcastType,
payload: parsed.payload
});
} else {
console.warn("Unhandled broadcast", parsed);
}
break;
}
default:
console.warn("unknown message type received", parsed);
console.error("Unhandled message", parsed);
}
} else if (message.data instanceof ArrayBuffer) {
const userNetworkingClientUpdate = UserNetworkingCodec.decodeUpdate(message.data);
this.clientUpdate(userNetworkingClientUpdate.id, userNetworkingClientUpdate);
this.config.clientUpdate(userNetworkingClientUpdate.id, userNetworkingClientUpdate);
} else {

@@ -360,8 +544,16 @@ console.error("Unhandled message type", message.data);

export {
CONNECTED_MESSAGE_TYPE,
DISCONNECTED_MESSAGE_TYPE,
IDENTITY_MESSAGE_TYPE,
PING_MESSAGE_TYPE,
PONG_MESSAGE_TYPE,
ReconnectingWebSocket,
USER_NETWORKING_AUTHENTICATION_FAILED_ERROR_TYPE,
USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE,
USER_NETWORKING_DISCONNECTED_MESSAGE_TYPE,
USER_NETWORKING_IDENTITY_MESSAGE_TYPE,
USER_NETWORKING_PING_MESSAGE_TYPE,
USER_NETWORKING_PONG_MESSAGE_TYPE,
USER_NETWORKING_SERVER_BROADCAST_MESSAGE_TYPE,
USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE,
USER_NETWORKING_UNKNOWN_ERROR,
USER_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE,
USER_NETWORKING_USER_PROFILE_MESSAGE_TYPE,
USER_NETWORKING_USER_UPDATE_MESSAGE_TYPE,
UserNetworkingClient,

@@ -368,0 +560,0 @@ UserNetworkingCodec,

import { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from "./ReconnectingWebSocket";
import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
import { CharacterDescription, FromUserNetworkingClientMessage, UserNetworkingServerErrorType } from "./UserNetworkingMessages";
export type UserNetworkingClientConfig = {
url: string;
sessionToken: string;
websocketFactory: WebsocketFactory;
statusUpdateCallback: (status: WebsocketStatus) => void;
assignedIdentity: (clientId: number) => void;
clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void;
clientProfileUpdated: (id: number, username: string, characterDescription: CharacterDescription) => void;
onServerError: (error: {
message: string;
errorType: UserNetworkingServerErrorType;
}) => void;
onServerBroadcast?: (broadcast: {
broadcastType: string;
payload: any;
}) => void;
};
export declare class UserNetworkingClient extends ReconnectingWebSocket {
private setIdentityCallback;
private clientUpdate;
constructor(url: string, websocketFactory: WebsocketFactory, statusUpdateCallback: (status: WebsocketStatus) => void, setIdentityCallback: (id: number) => void, clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void);
private config;
constructor(config: UserNetworkingClientConfig);
sendUpdate(update: UserNetworkingClientUpdate): void;
sendMessage(message: FromUserNetworkingClientMessage): void;
protected handleIncomingWebsocketMessage(message: MessageEvent): void;
}
import WebSocket from "ws";
import { UserData } from "./UserData";
import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
export type Client = {
import { UserIdentity, UserNetworkingServerError } from "./UserNetworkingMessages";
export type UserNetworkingServerClient = {
socket: WebSocket;
id: number;
lastPong: number;
update: UserNetworkingClientUpdate;
authenticatedUser: UserData | null;
};
export type UserNetworkingServerOptions = {
connectionLimit?: number;
onClientConnect: (clientId: number, sessionToken: string, userIdentity?: UserIdentity) => Promise<UserData | null> | UserData | null;
onClientUserIdentityUpdate: (clientId: number, userIdentity: UserIdentity) => Promise<UserData | null> | UserData | null;
onClientDisconnect: (clientId: number) => void;
};
export declare class UserNetworkingServer {
private clients;
private clientLastPong;
constructor();
heartBeat(): void;
pingClients(): void;
getId(): number;
private options;
private allClientsById;
private authenticatedClientsById;
private sendUpdatesIntervalTimer;
private pingClientsIntervalTimer;
private heartbeatIntervalTimer;
constructor(options: UserNetworkingServerOptions);
private heartBeat;
private pingClients;
private getId;
broadcastMessage(broadcastType: string, broadcastPayload: any): void;
connectClient(socket: WebSocket): void;
sendUpdates(): void;
private handleDisconnectedClient;
private handleUserAuth;
updateUserCharacter(clientId: number, userData: UserData): void;
private internalUpdateUser;
private handleUserUpdate;
private sendUpdates;
dispose(clientCloseError?: UserNetworkingServerError): void;
}
{
"name": "@mml-io/3d-web-user-networking",
"version": "0.0.0-experimental-4ca92b1-20231017",
"version": "0.0.0-experimental-4ec4338-20250205",
"publishConfig": {

@@ -22,13 +22,13 @@ "access": "public"

"dependencies": {
"ws": "^8.13.0"
"ws": "^8.18.0"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/express-ws": "^3.0.1",
"@types/node": "^20.5.9",
"@types/ws": "^8.5.5",
"express": "4.18.2",
"@types/express": "^4.17.21",
"@types/express-ws": "^3.0.4",
"@types/node": "^20.14.10",
"@types/ws": "^8.5.10",
"express": "4.19.2",
"express-ws": "5.0.2"
},
"gitHead": "0c91e4278b9950f8716bd43dbb823c13a6e2702c"
"gitHead": "10cb168cc7c41df283ad8f4f8fc579d08548497d"
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc