feat(server): wire device_id through delivery proto and v2 handlers

Add device_id field to FetchRequest, FetchWaitRequest, PeekRequest,
and AckRequest proto messages. V2 handlers now build composite
queue keys (identity_key + device_id) when device_id is provided,
enabling per-device fetch/ack scoping.
This commit is contained in:
2026-03-04 20:16:41 +01:00
parent eaca24397b
commit 799aab68fe
2 changed files with 75 additions and 16 deletions

View File

@@ -35,6 +35,19 @@ pub async fn handle_enqueue(state: Arc<ServerState>, ctx: RequestContext) -> Han
return HandlerResult::err(RpcStatus::RateLimited, "rate limit exceeded");
}
// Idempotency dedup: if message_id is provided and already seen, return the cached seq.
if !req.message_id.is_empty() {
if let Some(entry) = state.seen_message_ids.get(&req.message_id) {
let (cached_seq, _ts) = *entry;
let proto = v1::EnqueueResponse {
seq: cached_seq,
delivery_proof: Vec::new(),
duplicate: true,
};
return HandlerResult::ok(Bytes::from(proto.encode_to_vec()));
}
}
let svc = DeliveryService {
store: Arc::clone(&state.store),
waiters: Arc::clone(&state.waiters),
@@ -49,6 +62,12 @@ pub async fn handle_enqueue(state: Arc<ServerState>, ctx: RequestContext) -> Han
match svc.enqueue(domain_req) {
Ok(resp) => {
// Record message_id for dedup.
if !req.message_id.is_empty() {
let now = crate::auth::current_timestamp();
state.seen_message_ids.insert(req.message_id, (resp.seq, now));
}
// Fire hook.
let action = state.hooks.on_message_enqueue(&MessageEvent {
sender_identity: Some(identity_key),
@@ -64,6 +83,7 @@ pub async fn handle_enqueue(state: Arc<ServerState>, ctx: RequestContext) -> Han
let proto = v1::EnqueueResponse {
seq: resp.seq,
delivery_proof: resp.delivery_proof,
duplicate: false,
};
HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
}
@@ -87,12 +107,19 @@ pub async fn handle_fetch(state: Arc<ServerState>, ctx: RequestContext) -> Handl
waiters: Arc::clone(&state.waiters),
};
let base_key = if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
};
let recipient_key = if req.device_id.is_empty() {
base_key
} else {
DeliveryService::device_recipient_key(&base_key, &req.device_id)
};
let domain_req = FetchReq {
recipient_key: if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
},
recipient_key,
channel_id: req.channel_id,
limit: req.limit,
};
@@ -126,11 +153,16 @@ pub async fn handle_fetch_wait(state: Arc<ServerState>, ctx: RequestContext) ->
Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
};
let recipient_key = if req.recipient_key.is_empty() {
let base_key = if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
};
let recipient_key = if req.device_id.is_empty() {
base_key
} else {
DeliveryService::device_recipient_key(&base_key, &req.device_id)
};
let timeout_ms = if req.timeout_ms == 0 {
30_000
@@ -221,12 +253,19 @@ pub async fn handle_peek(state: Arc<ServerState>, ctx: RequestContext) -> Handle
waiters: Arc::clone(&state.waiters),
};
let base_key = if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
};
let recipient_key = if req.device_id.is_empty() {
base_key
} else {
DeliveryService::device_recipient_key(&base_key, &req.device_id)
};
let domain_req = PeekReq {
recipient_key: if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
},
recipient_key,
channel_id: req.channel_id,
limit: req.limit,
};
@@ -265,12 +304,19 @@ pub async fn handle_ack(state: Arc<ServerState>, ctx: RequestContext) -> Handler
waiters: Arc::clone(&state.waiters),
};
let base_key = if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
};
let recipient_key = if req.device_id.is_empty() {
base_key
} else {
DeliveryService::device_recipient_key(&base_key, &req.device_id)
};
let domain_req = AckReq {
recipient_key: if req.recipient_key.is_empty() {
identity_key
} else {
req.recipient_key
},
recipient_key,
channel_id: req.channel_id,
seq_up_to: req.seq_up_to,
};