notify node: userIDguard

This commit is contained in:
Thanakorn S
2026-05-27 13:12:18 +07:00
parent 82f66b0c41
commit dbbc89f8f2
4 changed files with 47 additions and 12 deletions
+17 -6
View File
@@ -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;
+1 -1
View File
@@ -17,7 +17,7 @@
if (typeof io === 'undefined') return;
window._socket = io('<?php echo NODE_PUBLIC_URL; ?>', {
query: { company_id: company_id },
query: { company_id: company_id, user_id: user_id, role: user_role },
reconnection: true,
reconnectionDelay: 2000,
});
+2
View File
@@ -128,6 +128,7 @@ $_usage_full = $_usage_max_pct >= 100;
<!-- Global Variables -->
<div id="session-context"
data-company-id="<?php echo htmlspecialchars($_SESSION['login_company_id'] ?? 0, ENT_QUOTES, 'UTF-8'); ?>"
data-user-id="<?php echo htmlspecialchars($_SESSION['login_user_id'] ?? 0, ENT_QUOTES, 'UTF-8'); ?>"
data-otp="<?php echo htmlspecialchars($_SESSION['otp'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
data-role="<?php echo htmlspecialchars($_SESSION['login_role'] ?? 'viewer', ENT_QUOTES, 'UTF-8'); ?>">
</div>
@@ -276,6 +277,7 @@ var server_url = '<?php echo $server_url; ?>';
var prop_limit = '<?php echo $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 = [];
+26 -4
View File
@@ -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
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);
}
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}`);