fix: socket io sockets (#2696)

This commit is contained in:
Daniel Salazar
2026-03-19 14:32:20 -07:00
committed by GitHub
parent c2e77d3503
commit b87f4988e1
11 changed files with 98 additions and 71 deletions
+1 -1
View File
@@ -24422,7 +24422,7 @@
},
"src/puter-js": {
"name": "@heyputer/puter.js",
"version": "2.2.12",
"version": "2.2.14",
"license": "Apache-2.0",
"dependencies": {
"@heyputer/kv.js": "^0.2.1",
+6 -3
View File
@@ -1546,7 +1546,7 @@ export function subdomain (req) {
return req.hostname.slice(0, -1 * (config.domain.length + 1));
}
export async function jwt_auth (req) {
export async function jwt_auth (req, authService) {
let token;
// HTTML Auth header
if ( req.header && req.header('Authorization') )
@@ -1583,8 +1583,11 @@ export async function jwt_auth (req) {
}
try {
const svc_auth = Context.get('services').get('auth');
const actor = await svc_auth.authenticate_from_token(token);
if ( ! authService ) {
throw new Error('jwt_auth requires authService');
}
const actor = await authService.authenticate_from_token(token);
if ( !actor.type?.constructor?.name === 'UserActorType' ) {
throw ({
@@ -66,9 +66,7 @@ class SocketioService extends BaseService {
if ( socket_specifier.room ) {
this.io.to(socket_specifier.room).emit(key, data);
} else if ( socket_specifier.socket ) {
const io = this.io.sockets.sockets.get(socket_specifier.socket);
if ( ! io ) continue;
io.emit(key, data);
this.io.to(socket_specifier.socket).emit(key, data);
}
}
}
+44 -29
View File
@@ -260,27 +260,36 @@ class WebServerService extends BaseService {
// TODO: ^ Replace above line with the following code:
await this.services.emit('install.socketio', { server });
const socketio = this.services.get('socketio').io;
const authService = this.services.get('auth');
// Socket.io middleware for authentication
socketio.use(async (socket, next) => {
if ( socket.handshake.auth.auth_token ) {
try {
let auth_res = await jwt_auth(socket);
// successful auth
socket.actor = auth_res.actor;
socket.user = auth_res.user;
socket.token = auth_res.token;
// join user room
socket.join(socket.user.id);
const authToken = socket.handshake?.auth?.auth_token;
if ( ! authToken ) {
next(new Error('socket auth token missing'));
return;
}
// setTimeout 0 is needed because we need to send
// the notifications after this handler is done
// setTimeout(() => {
// }, 1000);
next();
} catch (e) {
console.warn('socket auth err', e);
}
try {
const authRes = await jwt_auth(socket, authService);
// successful auth
socket.actor = authRes.actor;
socket.user = authRes.user;
socket.token = authRes.token;
// join user room
socket.join(socket.user.id);
// setTimeout 0 is needed because we need to send
// the notifications after this handler is done
// setTimeout(() => {
// }, 1000);
next();
} catch ( error ) {
console.warn('socket auth err', error);
const authError = error instanceof Error
? error
: new Error('socket auth failed');
next(authError);
}
});
@@ -674,6 +683,16 @@ class WebServerService extends BaseService {
app.use(function (req, res, next) {
const origin = req.headers.origin;
const subdomain = req.subdomains[req.subdomains.length - 1];
const isApiOrDavRequest =
config.experimental_no_subdomain ||
subdomain === 'api' ||
subdomain === 'dav';
const isCrossOriginAuthRoute =
req.path === '/signup' ||
req.path === '/login' ||
req.path.startsWith('/extensions/') ||
req.path.startsWith('/auth/oidc');
const is_site =
hostMatchesDomain(req.hostname, config.static_hosting_domain) ||
@@ -692,16 +711,16 @@ class WebServerService extends BaseService {
req.co_isolation_enabled
;
if ( req.path === '/signup' || req.path === '/login' || req.path.startsWith('/extensions/') || req.path.startsWith('/auth/oidc') ) {
if ( isCrossOriginAuthRoute || isApiOrDavRequest ) {
res.setHeader('Access-Control-Allow-Origin', origin ?? '*');
if ( origin ) {
res.vary('Origin');
}
}
// Website(s) to allow to connect
if (
config.experimental_no_subdomain ||
req.subdomains[req.subdomains.length - 1] === 'api' ||
req.subdomains[req.subdomains.length - 1] === 'dav'
) {
res.setHeader('Access-Control-Allow-Origin', origin ?? '*');
// Allow browser credentials on API/DAV cross-origin requests.
if ( isApiOrDavRequest && origin ) {
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// Request methods to allow
@@ -715,10 +734,6 @@ class WebServerService extends BaseService {
// Request headers to allow
res.header('Access-Control-Allow-Headers', allowed_headers.join(', '));
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
// res.setHeader('Access-Control-Allow-Credentials', true);
// Needed for SharedArrayBuffer
// NOTE: This is put behind a configuration flag because we
// need some experimentation to ensure the interface
+2 -1
View File
@@ -113,6 +113,7 @@ router.all('*', async function (req, res, next) {
}
const db = Context.get('services').get('database').get(DB_READ, 'default');
const authService = Context.get('services').get('auth');
// --------------------------------------
// POST to login/signup/logout
@@ -137,7 +138,7 @@ router.all('*', async function (req, res, next) {
let authed = false;
try {
try {
auth_user = await jwt_auth(req);
auth_user = await jwt_auth(req, authService);
auth_user = auth_user.user;
authed = true;
} catch (e) {
+20 -19
View File
@@ -34,14 +34,22 @@ class WSPushService extends BaseService {
this.svc_event.on('fs.write.*', this._on_fs_update.bind(this));
this.svc_event.on('fs.move.*', this._on_fs_move.bind(this));
this.svc_event.on('fs.pending.*', this._on_fs_pending.bind(this));
this.svc_event.on('fs.storage.upload-progress',
this._on_upload_progress.bind(this));
this.svc_event.on('fs.storage.progress.*',
this._on_upload_progress.bind(this));
this.svc_event.on('puter-exec.submission.done',
this._on_submission_done.bind(this));
this.svc_event.on('outer.gui.*',
this._on_outer_gui.bind(this));
this.svc_event.on(
'fs.storage.upload-progress',
this._on_upload_progress.bind(this),
);
this.svc_event.on(
'fs.storage.progress.*',
this._on_upload_progress.bind(this),
);
this.svc_event.on(
'puter-exec.submission.done',
this._on_submission_done.bind(this),
);
this.svc_event.on(
'outer.gui.*',
this._on_outer_gui.bind(this),
);
}
async _on_fs_create (key, data) {
@@ -251,16 +259,12 @@ class WSPushService extends BaseService {
const { socket_id } = metadata;
if ( ! socket_id ) {
this.log.warn('missing socket id', { metadata });
}
this.log.info(`socket id: ${ socket_id}`);
const svc_socketio = context.get('services').get('socketio');
if ( ! svc_socketio.has({ socket: socket_id }) ) {
console.warn('missing socket id', { metadata });
return;
}
const svc_socketio = context.get('services').get('socketio');
const ws_event_name = metadata.call_it_download
? 'download.progress' : 'upload.progress';
@@ -311,9 +315,6 @@ class WSPushService extends BaseService {
const svc_socketio = this.services.get('socketio');
for ( const user_id of user_id_list ) {
if ( ! svc_socketio.has({ room: user_id }) ) {
continue;
}
svc_socketio.send({ room: user_id }, key, response);
this.svc_event.emit(`sent-to-user.${key}`, {
user_id,
@@ -340,7 +341,7 @@ class WSPushService extends BaseService {
const kvStore = Context.get('services').get('puter-kvstore');
await kvStore.set({ key: key, value: ts });
} catch ( error ) {
this.log.error('Failed to update user timestamp in kvstore', { user_id, error: error.message });
console.error('Failed to update user timestamp in kvstore', { user_id, error: error.message });
}
}
+1
View File
@@ -49,6 +49,7 @@ const download = function (options) {
let xhr = new XMLHttpRequest();
xhr.open('post', (`${window.api_origin }/download`), true);
xhr.withCredentials = true;
xhr.setRequestHeader('Authorization', `Bearer ${ window.auth_token}`);
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "puter",
"version": "2.2.12",
"version": "2.2.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "puter",
"version": "2.2.12",
"version": "2.2.14",
"license": "Apache-2.0",
"dependencies": {
"@heyputer/kv.js": "^0.1.92",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@heyputer/puter.js",
"version": "2.2.12",
"version": "2.2.14",
"description": "Puter.js - A JavaScript library for interacting with Puter services.",
"homepage": "https://developer.puter.com",
"main": "src/index.js",
+19 -12
View File
@@ -92,6 +92,7 @@ const createDeferred = () => {
function initXhr (endpoint, APIOrigin, authToken, method = 'post', contentType = 'text/plain;actually=json', responseType = undefined) {
const xhr = new XMLHttpRequest();
xhr.open(method, APIOrigin + endpoint, true);
xhr.withCredentials = true;
if ( authToken )
{
xhr.setRequestHeader('Authorization', `Bearer ${ authToken}`);
@@ -280,16 +281,18 @@ function make_driver_method (arg_defs, driverInterface, driverName, driverMethod
async function driverCall (options, driverInterface, driverName, driverMethod, driverArgs, settings) {
const deferred = createDeferred();
driverCall_(options,
deferred.resolve,
deferred.reject,
driverInterface,
driverName,
driverMethod,
driverArgs,
undefined,
undefined,
settings);
driverCall_(
options,
deferred.resolve,
deferred.reject,
driverInterface,
driverName,
driverMethod,
driverArgs,
undefined,
undefined,
settings,
);
return await deferred.promise;
}
@@ -297,8 +300,12 @@ async function driverCall (options, driverInterface, driverName, driverMethod, d
// This function encapsulates the logic for sending a driver call request
async function driverCall_ (
options = {},
resolve_func, reject_func,
driverInterface, driverName, driverMethod, driverArgs,
resolve_func,
reject_func,
driverInterface,
driverName,
driverMethod,
driverArgs,
method,
contentType = 'text/plain;actually=json',
settings = {},
@@ -457,6 +457,7 @@ const upload = async function (items, dirPath, options = {}) {
// open request to server
xhr.open('post', (`${this.APIOrigin }/batch`), true);
xhr.withCredentials = true;
// set auth header
xhr.setRequestHeader('Authorization', `Bearer ${ this.authToken}`);