fix(protocol): report rejected card deliveries

Surface delivery failures instead of reporting success for rejected image and text cards. Reject responses without a delivery result.

Fixes #611
This commit is contained in:
DrSmoothl
2026-10-31 18:28:05 +08:00
parent 77d883f235
commit bd14b0a98e
3 changed files with 63 additions and 12 deletions
@@ -8,7 +8,7 @@
// reads {1:bussness_error_code, 5:ark_json}.
// Both return a server-built ark JSON string (the share card payload).
import type { pb, pb_optional, uint_32 } from '@snowluma/proton';
import type { pb, pb_optional, pb_repeated, uint_32 } from '@snowluma/proton';
export interface OidbBuddyRecommendArkReq {
uin?: pb<1, uint_32>;
@@ -67,4 +67,11 @@ export interface Oidb0xdc2_34Req {
meta?: pb<2, Oidb0xdc2Meta>;
}
export interface Oidb0xdc2_34Resp {}
export interface Oidb0xdc2CardResult {
errorCode?: pb<4, uint_32>;
errorMessage?: pb<5, string>;
}
export interface Oidb0xdc2_34Resp {
results?: pb_repeated<1, Oidb0xdc2CardResult>;
}
@@ -10,7 +10,7 @@ import type {
Oidb0xdc2_34Req,
Oidb0xdc2_34Resp,
} from '@snowluma/proto-defs/oidb-actions/contact-ark';
import { invokeOidb, type OidbSender } from '../../oidb-service';
import { invokeOidb, OidbError, type OidbSender } from '../../oidb-service';
const TUWEN_ARK_APPID = 100446242;
@@ -54,7 +54,22 @@ export namespace SendTuwenArk {
},
});
export const deserialize = (_ctx: Deps, _body: Oidb0xdc2_34Resp): void => undefined;
export const deserialize = (_ctx: Deps, body: Oidb0xdc2_34Resp): void => {
if (!body.results?.length) {
throw new Error('send_tuwen_ark response has no delivery result');
}
for (const result of body.results) {
if (result.errorCode) {
throw new OidbError(
result.errorCode,
result.errorMessage ?? '',
command,
subCommand,
);
}
}
};
export const encode = (env: OidbBase<Oidb0xdc2_34Req>): Uint8Array =>
protobuf_encode<OidbBase<Oidb0xdc2_34Req>>(env);
@@ -1,19 +1,15 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_encode } from '@snowluma/proton';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type { Oidb0xdc2_34Resp } from '@snowluma/proto-defs/oidb-actions/contact-ark';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import { SendTuwenArk } from '../../../src/oidb-services/contacts/send-tuwen-ark';
import { env, v, s, m } from '../_pb-oracle';
function makeSender() {
const respEnv: OidbBase<Oidb0xdc2_34Resp> = { command: 0xdc2, subCommand: 34, body: {} };
function makeSender(body = m(1, [...v(2, 10001), ...v(4, 0)])) {
const r: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData: Buffer.from(protobuf_encode<OidbBase<Oidb0xdc2_34Resp>>(respEnv)),
responseData: Buffer.from(env(0xdc2, 34, body, false), 'hex'),
};
return { sendRawPacket: vi.fn(async () => r) };
}
@@ -128,7 +124,40 @@ describe('SendTuwenArk namespace', () => {
expect(Buffer.from(bytes).toString('hex')).toBe(env(0xdc2, 34, body, false));
});
it('deserialize returns undefined (void response)', () => {
expect(SendTuwenArk.deserialize({} as any, {})).toBeUndefined();
it('rejects a delivery refusal even when the outer envelope succeeds', async () => {
const sender = makeSender(
m(1, [
...v(1, 0),
...v(2, 10001),
...s(3, ''),
...v(4, 901501),
...s(5, 'imagent service_error:319_[oidb] rule type not match appid'),
]),
);
await expect(
SendTuwenArk.invoke(sender, {
targetId: 10001,
peerType: 0,
title: 'title',
desc: '',
summary: '',
jumpUrl: 'https://example.com',
previewUrl: '',
}),
).rejects.toThrow(/901501.*rule type not match appid/);
});
it('checks every delivery result and requires an acknowledgement', () => {
const sender = makeSender();
expect(() => SendTuwenArk.deserialize(sender, {})).toThrow('no delivery result');
expect(() => SendTuwenArk.deserialize(sender, { results: [] })).toThrow(
'no delivery result',
);
expect(() =>
SendTuwenArk.deserialize(sender, {
results: [{ errorCode: 0 }, { errorCode: 901501, errorMessage: 'refused' }],
}),
).toThrow('refused');
expect(SendTuwenArk.deserialize(sender, { results: [{}] })).toBeUndefined();
});
});