Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces Socket.IO-based real-time NFC event handling (replacing polling) and expands the NFC payment flow into a “gate/continuous acceptance” mode, while also adding a new backend /JoinRoom API and updating mosaic creation to announce supply changes on-chain.
Changes:
- Add Socket.IO client/server wiring and switch NFC detection flows from polling to socket events.
- Implement “NFC gate session” reservation + repeated payment execution via
/SendTokenByNFC. - Add
/JoinRoomroute and update frontend Home to use it; update mosaic creation to also announce a supply-change transaction.
Reviewed changes
Copilot reviewed 21 out of 24 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| Frontend/src/Workspace/Pages/Payment/Payment.tsx | Switch payment UX toward NFC “gate mode” + Socket.IO event handling (currently contains unresolved merge conflicts + API mismatch). |
| Frontend/src/Workspace/Pages/NFC/NFC.tsx | Replace NFC polling with Socket.IO nfc:detected listener and navigate on success. |
| Frontend/src/Workspace/Pages/Home/Home.tsx | Use /JoinRoom and change navigation to Payment route (currently contains unresolved merge conflicts). |
| Frontend/src/Workspace/Pages/Home/Home.css | UI sizing/layout tweaks for Home header/buttons. |
| Frontend/src/Workspace/Components/RoomButton/RoomButton.css | Room button sizing constraints updates. |
| Frontend/src/App.tsx | Adjust Payment route param (currently contains unresolved merge conflicts). |
| Frontend/package.json / package-lock.json | Add socket.io-client. |
| Backend/package.json / package-lock.json | Add socket.io. |
| Backend/node_modules/.package-lock.json | Generated artifact added (should not be committed). |
| Backend/Workspace/Server.js | Wrap Express in HTTP server and attach Socket.IO with permissive CORS. |
| Backend/Workspace/Routes/NFC.js | Emit nfc:detected over Socket.IO when bridge posts UID (+ encrypted password). |
| Backend/Workspace/Routes/SendTokenByNFC.js | New NFC gate-session reserve + payment execution route; emits payment:result over Socket.IO. |
| Backend/Workspace/Routes/JoinRoom.js | New authenticated room-join endpoint. |
| Backend/Workspace/Routes/CreateRoom.js | Announce mosaic definition + supply-change transactions before DB insert. |
| Backend/Workspace/Tools/* (CreateTransferTx, SignAndAnnounce, LeftToken, SupplyMosaic, CreateMosaicTx) | Refactors Symbol transaction creation/signing and adds supply-change tx helper. |
| Backend/Workspace/tmp/SendTokenByNFC.js | Removes old tmp route implementation. |
| Backend/Workspace/Routes/Register.js | Adds logging (currently logs sensitive secrets). |
| Backend/NFC-bridge/NFC-bridge.py | Reads encrypted payload from card and posts {uid, encrypted_password} to backend. |
Files not reviewed (2)
- Backend/package-lock.json: Language not supported
- Frontend/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)
Backend/Workspace/Routes/NFC.js:83
nfc:detectedis emitted viaio.emit(...)to all connected Socket.IO clients, includingencrypted_password. Emit to a restricted room/channel (or authenticated socket) instead of broadcasting globally to prevent other clients from observing sensitive event data.
Backend/Workspace/Routes/NFC.js:76/NFCaccepts unauthenticated requests from the bridge. Add authentication for the bridge (shared secret / mTLS / IP allowlist) so arbitrary clients can’t inject NFC events into the system.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <<<<<<< HEAD | ||
| body: JSON.stringify({ | ||
| sendtoUserID: address, | ||
| Amount: token, | ||
| }), |
There was a problem hiding this comment.
Unresolved merge conflict markers remain around the reserve request body, leaving the final payload ambiguous. Resolve the conflict and ensure the payload matches the backend /SendTokenByNFC/NFC/Submit contract (currently { sendtoUserID, Amount }).
| const onPaymentResult = (payload: { reservationID: string, status: string, uid?: string, message?: string }) => { | ||
| // 他の端末の予約IDイベントは無視する | ||
| if (payload.reservationID !== reservationID) return; | ||
|
|
||
| if (payload.status === 'success') { | ||
| toast.success(`決済完了! (UID: ${payload.uid})`); | ||
| // 残高をマイナスして画面を更新 (モーダルは開いたまま) | ||
| setHandToken(prev => prev - token); |
There was a problem hiding this comment.
After a successful payment result, the UI subtracts token from handToken, but token is still editable during an active session and could have changed since the reservation was created. Consider freezing the amount once reservationID is set (or storing the reserved amount separately) so the displayed balance stays consistent with what was actually charged.
| const onPaymentResult = (payload: { reservationID: string, status: string, uid?: string, message?: string }) => { | |
| // 他の端末の予約IDイベントは無視する | |
| if (payload.reservationID !== reservationID) return; | |
| if (payload.status === 'success') { | |
| toast.success(`決済完了! (UID: ${payload.uid})`); | |
| // 残高をマイナスして画面を更新 (モーダルは開いたまま) | |
| setHandToken(prev => prev - token); | |
| const onPaymentResult = (payload: { reservationID: string; status: string; uid?: string; message?: string; amount?: number }) => { | |
| // 他の端末の予約IDイベントは無視する | |
| if (payload.reservationID !== reservationID) return; | |
| if (payload.status === 'success') { | |
| toast.success(`決済完了! (UID: ${payload.uid})`); | |
| // バックエンドから通知された決済額で残高を更新する | |
| if (typeof payload.amount === 'number') { | |
| setHandToken(prev => prev - payload.amount); | |
| } else { | |
| // 金額が不明な場合は残高を変更しない | |
| console.warn('Payment result received without amount; balance not updated.', payload); | |
| toast.error('決済額の取得に失敗しましたが、決済は完了しています。残高は再読み込みで更新されます。'); | |
| } |
| <<<<<<< HEAD | ||
| // バックエンドからの決済結果イベントをリッスン | ||
| const onPaymentResult = (payload: { reservationID: string, status: string, uid?: string, message?: string }) => { | ||
| // 他の端末の予約IDイベントは無視する | ||
| if (payload.reservationID !== reservationID) return; |
There was a problem hiding this comment.
There are still merge conflict markers inside the socket-based NFC useEffect, mixing it with the old polling code. This will not compile and makes the control flow unclear. Remove the markers and keep only one implementation (socket or polling).
|
|
||
| const balance = await LeftTokenAmount(userAddress, targetMosaicId, nodeUrl); | ||
|
|
||
| return res.status(200).json({ HandToken: Number(balance) }); |
There was a problem hiding this comment.
LeftTokenAmount returns a BigInt, but this code converts it to Number for HandToken. If balances can exceed Number.MAX_SAFE_INTEGER, this will lose precision. Consider returning a string (or separate display units) to the frontend instead of forcing Number(...).
| return res.status(200).json({ HandToken: Number(balance) }); | |
| return res.status(200).json({ HandToken: balance.toString() }); |
| router.post('/NFC', async (req, res) => { | ||
| const { uid, reservationID, encrypted_password } = req.body; | ||
| console.log("/NFC-API is running!", { uid, reservationID, encrypted_password }); | ||
|
|
There was a problem hiding this comment.
/SendTokenByNFC/NFC is missing the VCM auth middleware. Add authentication (or otherwise bind reservations to the caller) so only the reserving, logged-in session can trigger payments.
| const io = new SocketIOServer(server, { | ||
| cors: { | ||
| origin: '*', |
There was a problem hiding this comment.
Socket.IO is configured with cors: { origin: '*' }, allowing any website to open a socket and receive broadcast events like nfc:detected and payment:result. Restrict allowed origins (and ideally require authentication / namespace or room-based authorization) to prevent data exposure and event spoofing.
| const io = new SocketIOServer(server, { | |
| cors: { | |
| origin: '*', | |
| // Socket.IO CORS設定: 許可するオリジンを環境変数から取得 | |
| const rawAllowedOrigins = process.env.SOCKET_IO_ALLOWED_ORIGINS; | |
| const allowedOrigins = rawAllowedOrigins | |
| ? rawAllowedOrigins.split(',').map((o) => o.trim()).filter(Boolean) | |
| : ['http://localhost:3000']; | |
| const io = new SocketIOServer(server, { | |
| cors: { | |
| origin: (origin, callback) => { | |
| // origin が無い場合(例: 非ブラウザクライアント)は許可 | |
| if (!origin) { | |
| return callback(null, true); | |
| } | |
| if (allowedOrigins.includes(origin)) { | |
| return callback(null, true); | |
| } | |
| return callback(new Error('Not allowed by CORS')); | |
| }, | |
| credentials: true, |
| console.log("パスワードとペッパーを結合した文字列:", passwordWithPepper); | ||
|
|
||
| const encryptPassword = encrypt(process.env.PEPPER, password); | ||
| console.log("暗号化されたパスワード:", encryptPassword); |
There was a problem hiding this comment.
These new logs output sensitive credential material (password + pepper, and the encrypted password). Secrets should never be written to logs. Remove these logs and log only non-sensitive metadata if needed.
| console.log("パスワードとペッパーを結合した文字列:", passwordWithPepper); | |
| const encryptPassword = encrypt(process.env.PEPPER, password); | |
| console.log("暗号化されたパスワード:", encryptPassword); | |
| // セキュリティのため、パスワードやペッパーを含む値はログに出力しない | |
| const encryptPassword = encrypt(process.env.PEPPER, password); | |
| // セキュリティのため、暗号化されたパスワードもログに出力しない |
| const res = await fetch('/SendTokenByNFC/', { | ||
| method: 'POST', | ||
| headers: { "Content-Type": "application/json" }, | ||
| credentials: "include", | ||
| body: JSON.stringify({ roomId }), | ||
| body: JSON.stringify({ roomName: decodedRoomName }), |
There was a problem hiding this comment.
fetchRooms() POSTs to /SendTokenByNFC/, but the backend SendTokenByNFC route does not define a POST / handler (only GET / and POST /NFC/Submit + POST /NFC). This will 404 and roomIcon won’t load. Use the correct room-details endpoint or add a backend handler.
Frontend/src/App.tsx
Outdated
| <<<<<<< HEAD | ||
| <Route path="/Payment/:roomId" element={<Payment />} /> | ||
| ======= | ||
| <Route path="/Payment/:roomName" element={<Payment />} /> | ||
| >>>>>>> 6028147d9933c8436467e0b19dd758729f43c4b6 |
There was a problem hiding this comment.
Unresolved git merge conflict markers remain in the route definition for Payment (:roomId vs :roomName). This will break the router at build time. Resolve the conflict and ensure it matches the useParams usage in Payment.tsx.
| <<<<<<< HEAD | |
| <Route path="/Payment/:roomId" element={<Payment />} /> | |
| ======= | |
| <Route path="/Payment/:roomName" element={<Payment />} /> | |
| >>>>>>> 6028147d9933c8436467e0b19dd758729f43c4b6 | |
| <Route path="/Payment/:roomId" element={<Payment />} /> |
| ======= | ||
| body: JSON.stringify({ address, token, password }), | ||
| >>>>>>> 6028147d9933c8436467e0b19dd758729f43c4b6 |
There was a problem hiding this comment.
Unresolved merge conflict markers remain inside HandleNfcPayment around the POST body, leaving two incompatible payloads. Resolve the conflict and keep only the payload expected by /SendTokenByNFC/NFC (e.g. { uid, reservationID, encrypted_password }).
| ======= | |
| body: JSON.stringify({ address, token, password }), | |
| >>>>>>> 6028147d9933c8436467e0b19dd758729f43c4b6 |
No description provided.