Require targetUserId for item transfers

This commit is contained in:
Jage9
2026-02-28 20:26:40 -05:00
parent daab7bb759
commit 449f2c9530
5 changed files with 16 additions and 12 deletions

View File

@@ -387,7 +387,7 @@ export type OutgoingMessage =
| { type: 'item_drop'; itemId: string; x: number; y: number } | { type: 'item_drop'; itemId: string; x: number; y: number }
| { type: 'item_delete'; itemId: string } | { type: 'item_delete'; itemId: string }
| { type: 'item_transfer_targets'; itemId: string } | { type: 'item_transfer_targets'; itemId: string }
| { type: 'item_transfer'; itemId: string; targetId?: string; targetUserId?: string } | { type: 'item_transfer'; itemId: string; targetUserId: string }
| { type: 'item_use'; itemId: string } | { type: 'item_use'; itemId: string }
| { type: 'item_secondary_use'; itemId: string } | { type: 'item_secondary_use'; itemId: string }
| { type: 'item_piano_note'; itemId: string; keyId: string; midi: number; on: boolean } | { type: 'item_piano_note'; itemId: string; keyId: string; midi: number; on: boolean }

View File

@@ -29,7 +29,7 @@ This is a behavior guide for packet semantics beyond raw schemas.
- `ping`: latency measurement. - `ping`: latency measurement.
- `item_add`, `item_pickup`, `item_drop`, `item_delete`, `item_use`, `item_update`: item actions. - `item_add`, `item_pickup`, `item_drop`, `item_delete`, `item_use`, `item_update`: item actions.
- `item_transfer_targets`: request transfer target accounts for one item (includes online + offline active users, excluding current owner). - `item_transfer_targets`: request transfer target accounts for one item (includes online + offline active users, excluding current owner).
- `item_transfer`: transfer item ownership to another account (supports `targetUserId`; `targetId` remains accepted for compatibility). - `item_transfer`: transfer item ownership to another account (`targetUserId` required).
- `item_secondary_use`: trigger type-specific secondary action when implemented. - `item_secondary_use`: trigger type-specific secondary action when implemented.
- `item_piano_note`: realtime piano note on/off for active piano use mode. - `item_piano_note`: realtime piano note on/off for active piano use mode.
- `item_piano_recording`: piano record/playback control (`toggle_record`, `playback`, `stop_playback`). - `item_piano_recording`: piano record/playback control (`toggle_record`, `playback`, `stop_playback`).

View File

@@ -140,8 +140,7 @@ class ItemDeletePacket(BasePacket):
class ItemTransferPacket(BasePacket): class ItemTransferPacket(BasePacket):
type: Literal["item_transfer"] type: Literal["item_transfer"]
itemId: str itemId: str
targetId: str | None = None targetUserId: str
targetUserId: str | None = None
class ItemTransferTargetsPacket(BasePacket): class ItemTransferTargetsPacket(BasePacket):

View File

@@ -2545,11 +2545,7 @@ class SignalingServer:
if not can_transfer_any and not can_transfer_own: if not can_transfer_any and not can_transfer_own:
await self._send_item_result(client, False, "transfer", "Not authorized to transfer this item.", item.id) await self._send_item_result(client, False, "transfer", "Not authorized to transfer this item.", item.id)
return return
target_user_id = str(packet.targetUserId or "").strip() target_user_id = str(packet.targetUserId).strip()
if not target_user_id and packet.targetId:
target = self._get_client_by_id(packet.targetId)
if target and target.authenticated and target.user_id:
target_user_id = target.user_id
if not target_user_id: if not target_user_id:
await self._send_item_result(client, False, "transfer", "Target user is not available.", item.id) await self._send_item_result(client, False, "transfer", "Target user is not available.", item.id)
return return

View File

@@ -377,7 +377,10 @@ async def test_item_transfer_updates_item_owner(monkeypatch: pytest.MonkeyPatch)
monkeypatch.setattr(server, "_broadcast_item", fake_broadcast_item) monkeypatch.setattr(server, "_broadcast_item", fake_broadcast_item)
monkeypatch.setattr(server, "_broadcast", fake_broadcast) monkeypatch.setattr(server, "_broadcast", fake_broadcast)
await server._handle_message(owner, json.dumps({"type": "item_transfer", "itemId": item.id, "targetId": target.id})) await server._handle_message(
owner,
json.dumps({"type": "item_transfer", "itemId": item.id, "targetUserId": target.user_id}),
)
assert item.createdBy == target.user_id assert item.createdBy == target.user_id
assert item.createdByName == target.username assert item.createdByName == target.username
@@ -439,7 +442,10 @@ async def test_item_transfer_allows_self_target_for_transfer_any(monkeypatch: py
monkeypatch.setattr(server, "_send", fake_send) monkeypatch.setattr(server, "_send", fake_send)
monkeypatch.setattr(server, "_broadcast_item", fake_broadcast_item) monkeypatch.setattr(server, "_broadcast_item", fake_broadcast_item)
await server._handle_message(actor, json.dumps({"type": "item_transfer", "itemId": item.id, "targetId": actor.id})) await server._handle_message(
actor,
json.dumps({"type": "item_transfer", "itemId": item.id, "targetUserId": actor.user_id}),
)
assert item.createdBy == actor.user_id assert item.createdBy == actor.user_id
assert item.createdByName == actor.username assert item.createdByName == actor.username
@@ -677,7 +683,10 @@ async def test_item_transfer_rejects_when_not_authorized(monkeypatch: pytest.Mon
monkeypatch.setattr(server, "_send", fake_send) monkeypatch.setattr(server, "_send", fake_send)
await server._handle_message(owner, json.dumps({"type": "item_transfer", "itemId": item.id, "targetId": target.id})) await server._handle_message(
owner,
json.dumps({"type": "item_transfer", "itemId": item.id, "targetUserId": target.user_id}),
)
assert item.createdBy == owner.user_id assert item.createdBy == owner.user_id
assert send_payloads assert send_payloads