diff --git a/package.json b/package.json index e2d71ff..1291c64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@textbee/sdk", - "version": "0.3.1", + "version": "0.4.0", "description": "Official JavaScript SDK for textbee.dev, the open source SMS gateway", "license": "MIT", "packageManager": "pnpm@9.14.2", diff --git a/src/index.ts b/src/index.ts index ee70575..7c52bf5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,8 @@ export type { ListMessagesOptions, Message, MessageDevice, + MessageIssueEntry, + MessageIssueSource, MessageList, MessageListMeta, SendSmsImmediateResponse, diff --git a/src/types.ts b/src/types.ts index c3b560d..dc73b6e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -168,6 +168,22 @@ export interface MessageDevice { enabled?: boolean } +/** Where a failure was reported from. */ +export type MessageIssueSource = 'push' | 'device' | 'server' + +/** One failure a message had. Kept after the message later succeeds. */ +export interface MessageIssueEntry { + code: string + message?: string + /** When the failure happened. ISO date. */ + at: string + source: MessageIssueSource + /** Set on entries rebuilt from old message fields. */ + backfilled?: boolean + /** True when the entry is a delivery report that did not confirm delivery. The message was sent; this is not a send failure. */ + deliveryReport?: boolean +} + export interface Message { _id: string message: string @@ -199,10 +215,15 @@ export interface Message { dispatchedAt?: string sentAt?: string deliveredAt?: string + /** When the message failed. Present only while status is `failed`. */ failedAt?: string receivedAt?: string + /** Current failure code. Present only while status is `failed` (test accounts can keep `FCM_SEND_SKIPPED`). */ errorCode?: string + /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string + /** Every failure the message had, plus delivery reports that did not confirm delivery (`deliveryReport: true`, not failures). Oldest first. At most 15: the first 5 and the newest 10. */ + issueHistory?: MessageIssueEntry[] simSubscriptionId?: number smsBatch?: string createdAt?: string diff --git a/test/client.test.ts b/test/client.test.ts index 78aa086..cc2f108 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Textbee, TextbeeError } from '../src/index' +import type { MessageIssueEntry } from '../src/index' const pkg = JSON.parse( readFileSync(new URL('../package.json', import.meta.url), 'utf8'), @@ -252,6 +253,25 @@ describe('response envelopes', () => { expect(url.searchParams.get('type')).toBeNull() }) + it('types issueHistory on a message that failed and later succeeded', async () => { + const entry: MessageIssueEntry = { + code: 'FCM_SEND_FAILED', + message: 'push failed', + at: '2026-10-01T10:00:00.000Z', + source: 'push', + backfilled: true, + } + const page = { + data: [{ _id: 'sms-2', message: 'hi', type: 'SENT', status: 'sent', issueHistory: [entry] }], + meta: {}, + } + fetchMock.mockImplementation(async () => respond(200, page)) + + const result = await client().getMessages() + + expect(result.data[0]?.issueHistory).toEqual([entry]) + }) + it('sends no query at all for a bare account-level getMessages()', async () => { fetchMock.mockImplementation(async () => respond(200, { data: [], meta: {} }))