From dbbc89f8f2883d47e791359864074e3eeb5f950f Mon Sep 17 00:00:00 2001 From: Thanakorn S Date: Wed, 27 May 2026 13:12:18 +0700 Subject: [PATCH] notify node: userIDguard --- app/assets/utils/notify_node.php | 23 +++++++++++++++++------ app/include_ending.php | 2 +- app/include_topbar.php | 2 ++ nodejs/server.js | 32 +++++++++++++++++++++++++++----- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/app/assets/utils/notify_node.php b/app/assets/utils/notify_node.php index 6a3fd25..4092cf8 100644 --- a/app/assets/utils/notify_node.php +++ b/app/assets/utils/notify_node.php @@ -9,15 +9,15 @@ * @param array $data Payload sent to the browser * @param int $company_id Scopes the broadcast to one company's room */ -function notify_node(string $event, array $data, int $company_id): void +function notify_node(string $event, array $data, int $company_id, string $target = '', int $user_id = 0): void { if ($company_id <= 0) return; - $body = json_encode([ - 'event' => $event, - 'data' => $data, - 'company_id' => $company_id, - ]); + $payload = ['event' => $event, 'data' => $data, 'company_id' => $company_id]; + if ($target) $payload['target'] = $target; + if ($user_id) $payload['user_id'] = $user_id; + + $body = json_encode($payload); $ch = curl_init(defined('NODE_EMIT_URL') ? NODE_EMIT_URL : 'http://127.0.0.1:3000/emit'); curl_setopt_array($ch, [ @@ -45,6 +45,7 @@ function notify_node(string $event, array $data, int $company_id): void * @param string $message Detail line, e.g. 'PO-2605-00042 has been approved.' * @param string $type 'success' | 'info' | 'warning' | 'danger' (default 'info') */ +// Company-wide notification — all users in the company see the toast. function emit_notification(int $company_id, string $title, string $message, string $type = 'info'): void { notify_node('notification', [ @@ -54,6 +55,16 @@ function emit_notification(int $company_id, string $title, string $message, stri ], $company_id); } +// Document notification — the acting user + all admins/owners see the toast. Other staff/viewers do not. +function emit_notification_user(int $company_id, int $user_id, string $title, string $message, string $type = 'info'): void +{ + notify_node('notification', [ + 'title' => $title, + 'message' => $message, + 'type' => $type, + ], $company_id, 'user', $user_id); +} + function gl_posted_payload(string $doc_type, int $id, string $action, array $lines): array { $has_revenue = false; diff --git a/app/include_ending.php b/app/include_ending.php index f381c29..49f50c8 100644 --- a/app/include_ending.php +++ b/app/include_ending.php @@ -17,7 +17,7 @@ if (typeof io === 'undefined') return; window._socket = io('', { - query: { company_id: company_id }, + query: { company_id: company_id, user_id: user_id, role: user_role }, reconnection: true, reconnectionDelay: 2000, }); diff --git a/app/include_topbar.php b/app/include_topbar.php index 7940a4b..371477d 100644 --- a/app/include_topbar.php +++ b/app/include_topbar.php @@ -128,6 +128,7 @@ $_usage_full = $_usage_max_pct >= 100;
@@ -276,6 +277,7 @@ var server_url = ''; var prop_limit = ''; var user_role = document.getElementById('session-context')?.dataset.role || 'viewer'; var company_id = parseInt(document.getElementById('session-context')?.dataset.companyId || '0', 10); +var user_id = parseInt(document.getElementById('session-context')?.dataset.userId || '0', 10); // ── Bell notifications ──────────────────────────────────────────────────────── var _notif_items = []; diff --git a/nodejs/server.js b/nodejs/server.js index 2852e07..58e2566 100644 --- a/nodejs/server.js +++ b/nodejs/server.js @@ -22,15 +22,26 @@ app.post('/emit', (req, res) => { return res.status(403).json({ ok: false, message: 'Forbidden' }); } - const { event, data, company_id } = req.body; + const { event, data, company_id, target, user_id } = req.body; if (!event || !company_id) { return res.status(400).json({ ok: false, message: 'event and company_id required' }); } - // Broadcast only to the room for this company - io.to(`company_${company_id}`).emit(event, data); + if (target === 'user' && user_id) { + // Notify the acting user on all their tabs + all admins (excluding the acting user to avoid duplicates) + io.to(`user_${user_id}`).emit(event, data); + io.to(`admin_${company_id}`).except(`user_${user_id}`).emit(event, data); + console.log(`[emit] company=${company_id} user=${user_id} event=${event}`, data); + } else if (target === 'admin') { + // Admins and owners only + io.to(`admin_${company_id}`).emit(event, data); + console.log(`[emit] company=${company_id} admin-only event=${event}`, data); + } else { + // Company-wide — stock events, GL events, scheduler alerts + io.to(`company_${company_id}`).emit(event, data); + console.log(`[emit] company=${company_id} event=${event}`, data); + } - console.log(`[emit] company=${company_id} event=${event}`, data); res.json({ ok: true }); }); @@ -50,6 +61,8 @@ app.get('/health', (req, res) => { // io.on('connection', (socket) => { const company_id = socket.handshake.query.company_id; + const user_id = socket.handshake.query.user_id; + const role = socket.handshake.query.role || 'viewer'; if (!company_id) { socket.disconnect(); @@ -57,7 +70,16 @@ io.on('connection', (socket) => { } socket.join(`company_${company_id}`); - console.log(`[connect] socket=${socket.id} company=${company_id}`); + + if (user_id) { + socket.join(`user_${user_id}`); + } + + if (role === 'admin' || role === 'owner') { + socket.join(`admin_${company_id}`); + } + + console.log(`[connect] socket=${socket.id} company=${company_id} user=${user_id} role=${role}`); socket.on('disconnect', () => { console.log(`[disconnect] socket=${socket.id}`);