@teamlearners/clawops 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/solapi/_concurrency.ts","../../src/solapi/_message-type.ts","../../src/solapi/fallback-text.ts","../../src/solapi/_fallback.ts","../../src/solapi/sweep.ts","../../src/solapi/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** 한 번에 띄우는 ClawOps 요청 수. 솔라피의 배치 한 번이 우리에겐 요청 N 번이 된다 */\nexport const DEFAULT_CONCURRENCY = 10;\n\n/**\n * 순서를 지키면서 동시 요청 수를 제한한다.\n *\n * 배치를 끊어 기다리면 한 건의 재시도가 같은 배치 전체를 붙잡으므로,\n * 워커가 공용 커서를 당겨 써서 빈 슬롯이 생기지 않게 한다.\n */\nexport async function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n run: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let cursor = 0;\n const worker = async (): Promise<void> => {\n for (let index = cursor++; index < items.length; index = cursor++) {\n results[index] = await run(items[index]!);\n }\n };\n // limit 이 0 이면 워커가 하나도 안 떠서 전부 조용히 누락된다.\n // `concurrency: Number(process.env.X)` 가 미설정 시 0 이 되는 흔한 경로가 있다\n const workers = Math.min(Math.max(1, limit), items.length);\n await Promise.all(Array.from({ length: workers }, worker));\n return results;\n}\n","/**\n * SMS 본문 상한. **EUC-KR 90byte 다** — UTF-8 이 아니다.\n *\n * 서버의 판정 기준(`app/src/services/messages.ts` 의 `SMS_MAX_EUCKR_BYTES`)과 같아야 한다.\n * 어긋나면 우리가 sms 라고 보낸 건이 서버에서 `body_too_long` 으로 거절된다.\n *\n * 솔라피의 SMS 기준(90byte EUC-KR)과 **동일하다**. 옮겨오는 코드의 분기 로직을 그대로 쓸 수 있다.\n */\nexport const SMS_MAX_EUCKR_BYTES = 90;\n\n/**\n * EUC-KR 기준 바이트 수. ASCII 1byte, 그 외 2byte.\n *\n * EUC-KR 로 표현할 수 없는 문자는 게이트웨이가 `?` 한 글자로 치환하므로 실제로는 1byte 지만\n * 2byte 로 세어 과대평가 쪽에 둔다 — 이 방향의 오차는 본문 잘림을 만들지 않는다.\n */\nexport function euckrByteLength(text: string): number {\n let bytes = 0;\n for (const ch of text) bytes += ch.codePointAt(0)! < 0x80 ? 1 : 2;\n return bytes;\n}\n\n/** solapi 는 phoneNumberSchema 로 하이픈을 제거한다. 응답과 맞추려면 같은 규칙이 필요하다 */\nexport const normalizePhone = (value: string): string => value.replace(/-/g, '');\n\n/**\n * ClawOps 로 보낼 때의 메시지 타입.\n * 명시된 sms/lms 는 그대로 존중하고, 그 외에는 서버와 같은 규칙으로 정한다.\n */\n/** 메시지 타입을 한 가지 표기로 통일한다. 없으면 빈 문자열 */\nexport const upperType = (message: { type?: unknown }): string =>\n message.type === undefined ? '' : String(message.type).toUpperCase();\n\nexport function clawopsType(message: {\n type?: unknown;\n text?: string | null;\n subject?: string | null;\n}): 'sms' | 'lms' {\n const type = upperType(message);\n if (type === 'SMS') return 'sms';\n if (type === 'LMS') return 'lms';\n if (message.subject !== undefined && message.subject !== null && message.subject !== '') {\n return 'lms';\n }\n return euckrByteLength(message.text ?? '') > SMS_MAX_EUCKR_BYTES ? 'lms' : 'sms';\n}\n","import type { SolapiMessageService, RequestSendMessagesSchema } from 'solapi';\n\n/** 제네릭을 거쳐야 유니온(단건 | 배열)에 분배된다 */\ntype Elem<T> = T extends readonly (infer U)[] ? U : T;\nexport type SolapiMessage = Elem<RequestSendMessagesSchema>;\n\nconst VARIABLE_KEY = /^#\\{.+\\}$/;\nconst LEFTOVER = /#\\{[^}]*\\}/g;\n\n/** solapi SDK 와 같은 규칙: `name` → `#{name}`, 이미 `#{...}` 면 그대로 */\nconst asKey = (key: string): string => (VARIABLE_KEY.test(key) ? key : `#{${key}}`);\n\nexport function render(content: string, variables: Record<string, string> = {}): string {\n let out = content;\n for (const [key, value] of Object.entries(variables)) {\n out = out.split(asKey(key)).join(value);\n }\n return out;\n}\n\n/** 치환되지 않고 남은 변수. 하나라도 있으면 발송하지 않는다 */\nexport const leftovers = (text: string): string[] => text.match(LEFTOVER) ?? [];\n\nexport type FallbackSource = 'customFields' | 'template' | 'text';\n\n/**\n * 문구 복원에 필요한 최소 모양.\n *\n * 발송 요청(`SolapiMessage`)과 조회 결과(`storedMessage`)를 둘 다 받는다 —\n * 조회 결과의 `kakaoOptions` 는 서버 정규화 포맷이라 `Record<string, unknown>` 으로 온다.\n */\nexport interface FallbackInput {\n type?: unknown;\n text?: string | null;\n customFields?: Record<string, string> | null;\n kakaoOptions?: Record<string, unknown> | null;\n}\n\n/** 정규화 포맷이든 요청 포맷이든 템플릿 참조를 안전하게 꺼낸다 */\nfunction templateRef(kakaoOptions: unknown): {\n templateId?: string;\n variables?: Record<string, string>;\n} {\n if (kakaoOptions === null || typeof kakaoOptions !== 'object') return {};\n const options = kakaoOptions as Record<string, unknown>;\n const templateId = typeof options.templateId === 'string' ? options.templateId : undefined;\n\n let variables: Record<string, string> | undefined;\n const raw = options.variables;\n if (raw !== null && typeof raw === 'object') {\n const entries = Object.entries(raw as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n );\n if (entries.length > 0) variables = Object.fromEntries(entries);\n }\n return { templateId, variables };\n}\n\nexport type FallbackText =\n | { ok: true; text: string; source: FallbackSource }\n | { ok: false; reason: 'unresolved_variables'; unresolved: string[]; source: FallbackSource }\n | { ok: false; reason: 'no_template_content' | 'no_text'; source: FallbackSource };\n\nexport interface ResolveOptions {\n /** 대체 문구를 읽어올 customFields 키 */\n field?: string;\n /**\n * 템플릿 본문 캐시. 호출자가 수명을 정한다.\n *\n * 값이 아니라 진행 중인 조회를 담는다 — 여러 건이 동시에 같은 템플릿을 찾을 때\n * 완료를 기다리며 캐시가 비어 있으면 같은 요청이 그 수만큼 나간다.\n */\n cache?: Map<string, Promise<string | undefined>>;\n}\n\nexport const DEFAULT_FALLBACK_FIELD = 'clawopsFallbackText';\n\n/**\n * 대체발송에 쓸 문구를 만든다.\n *\n * 솔라피는 이 문구를 콘솔(템플릿별 대체발송 설정)에만 두고 API 로 노출하지 않는다.\n * 알림톡은 대개 `text` 없이 `variables` 만 보내므로 템플릿 본문을 조회해 직접 치환한다.\n */\nexport async function resolveFallbackText(\n message: FallbackInput,\n solapi: SolapiMessageService,\n options: ResolveOptions = {},\n): Promise<FallbackText> {\n const field = options.field ?? DEFAULT_FALLBACK_FIELD;\n\n // 고객이 명시한 문구가 있으면 그것이 정본이다\n const explicit = message.customFields?.[field];\n if (explicit !== undefined) return finish(explicit, 'customFields');\n\n // 그 외에는 `kakaoOptions.templateId` 의 **존재**가 출처를 정한다. `type` 으로 판정하면 안 된다 —\n // solapi 의 `type` 은 optional 이고 서버가 vendor 옵션으로 추론하므로, 생략한 알림톡이\n // 템플릿 경로를 건너뛰고 `no_text` 로 떨어진다(실호출로 확인).\n //\n // NSA(네이버 스마트알림)는 자연히 빠진다 — templateId 가 `naverOptions` 에 있어\n // `kakaoOptions` 만 보는 templateRef 에 걸리지 않는다. 조회 API 도 달라서 여기서 다루면 항상 실패한다.\n const { templateId, variables } = templateRef(message.kakaoOptions);\n if (templateId !== undefined) {\n const cache = options.cache;\n let pending = cache?.get(templateId);\n if (pending === undefined) {\n pending = solapi.getKakaoAlimtalkTemplate(templateId).then((template) => template.content);\n // await 하기 전에 등록해야 뒤따르는 호출이 같은 조회를 기다린다\n cache?.set(templateId, pending);\n // 실패는 캐시하지 않는다. 남겨두면 일시적 오류가 영구 실패가 된다\n pending.catch(() => cache?.delete(templateId));\n }\n const content = await pending;\n if (content === undefined) {\n return { ok: false, reason: 'no_template_content', source: 'template' };\n }\n return finish(render(content, variables), 'template');\n }\n\n if (message.text === undefined || message.text === null) {\n return { ok: false, reason: 'no_text', source: 'text' };\n }\n return finish(message.text, 'text');\n}\n\nfunction finish(text: string, source: FallbackSource): FallbackText {\n const unresolved = leftovers(text);\n return unresolved.length > 0\n ? { ok: false, reason: 'unresolved_variables', unresolved, source }\n : { ok: true, text, source };\n}\n","import type { SolapiMessageService } from 'solapi';\nimport type { ClawOps } from '../client.js';\nimport { clawopsType, normalizePhone } from './_message-type.js';\nimport {\n resolveFallbackText,\n type FallbackInput,\n type FallbackSource,\n type FallbackText,\n} from './fallback-text.js';\n\nexport type BlockedReason = Extract<FallbackText, { ok: false }>;\n\n/**\n * 대체발송 한 건의 결말.\n *\n * 접수 실패 경로(`send()` 안)와 발송 실패 경로(스윕)가 같은 일을 하므로 여기서 한 번만 정의한다.\n * 두 곳에 복사해 두면 한쪽에만 가드가 붙는 일이 실제로 벌어진다.\n */\nexport type FallbackDelivery =\n /** 문구를 만들지 못했다 */\n | { status: 'blocked'; reason: BlockedReason }\n /** 문자로 나갔다 */\n | { status: 'sent'; source: FallbackSource; text: string; messageId: string }\n /** 문구는 만들었지만 ClawOps 가 거절했다 */\n | { status: 'send_failed'; source: FallbackSource; text: string; error: unknown };\n\nexport interface FallbackContext {\n clawops: ClawOps;\n /** 대체 문구를 읽어올 customFields 키 */\n field?: string;\n /** 템플릿 본문 캐시 */\n cache: Map<string, Promise<string | undefined>>;\n}\n\nexport interface FallbackRequest {\n /** 문구를 복원할 원본. 발송 요청이든 조회된 메시지든 상관없다 */\n source: FallbackInput & { subject?: string | null };\n to: string;\n from: string;\n /** 같은 건을 다시 보내지 않도록. 스윕처럼 재실행되는 경로만 채운다 */\n idempotencyKey?: string;\n}\n\n/**\n * 문구를 복원해 문자 한 건을 보낸다.\n *\n * **두 단계 모두 이 안에서 격리한다.** 밖으로 던지면 호출자가 배치 단위로 실패하는데,\n * 그 시점엔 이미 다른 건이 나가 있어 기록을 잃고 재시도하면 중복 발송이 된다.\n * 스윕에서는 더 나쁘다 — 한 건이 던지면 커서가 안 옮겨져 다음 주기가 같은 건을 다시 만나고,\n * 대체발송이 영영 진행되지 않는다.\n */\nexport async function deliverFallback(\n request: FallbackRequest,\n solapi: SolapiMessageService,\n context: FallbackContext,\n): Promise<FallbackDelivery> {\n let resolved: FallbackText;\n try {\n resolved = await resolveFallbackText(request.source, solapi, {\n field: context.field,\n cache: context.cache,\n });\n } catch {\n resolved = { ok: false, reason: 'no_template_content', source: 'template' };\n }\n if (!resolved.ok) return { status: 'blocked', reason: resolved };\n\n const subject = request.source.subject ?? undefined;\n try {\n const created = await context.clawops.messages.create({\n to: normalizePhone(request.to),\n from: normalizePhone(request.from),\n body: resolved.text,\n type: clawopsType({ text: resolved.text, subject }),\n subject,\n idempotencyKey: request.idempotencyKey,\n });\n return {\n status: 'sent',\n source: resolved.source,\n text: resolved.text,\n messageId: created.messageId,\n };\n } catch (error) {\n return { status: 'send_failed', source: resolved.source, text: resolved.text, error };\n }\n}\n","import type { SolapiMessageService } from 'solapi';\nimport type { ClawOps } from '../client.js';\nimport { mapWithConcurrency, DEFAULT_CONCURRENCY } from './_concurrency.js';\nimport { deliverFallback } from './_fallback.js';\nimport type { FallbackInput, FallbackSource, FallbackText } from './fallback-text.js';\n\n/** 발송 시 심는 마커. 이 값이 있는 건만 대체발송 후보다 */\nexport const FALLBACK_MARKER_FIELD = 'clawopsFallback';\nexport const FALLBACK_MARKER_VALUE = '1';\n\n/**\n * 기본 대상 코드 — **수신자 사유만**.\n *\n * 설정 오류(3101 발신프로필 무효·3105 미등록 템플릿·3106 유효하지 않은 채널)를 문자로 덮으면\n * 알림톡이 깨진 걸 고객이 몇 달간 모른다. 3108(발송 가능 시간 아님)은 문자로 대체하면\n * 21시 이후 발송이 되어 야간 규제에 걸린다. 둘 다 `onBlocked` 로 알리기만 한다.\n */\nexport const DEFAULT_FALLBACK_CODES = ['3104', '3107', '3102'] as const;\n\n/**\n * 훑을 메시지 타입. 기본은 알림톡만이다.\n *\n * 발송 시 마커는 `kakaoOptions` 가 붙은 모든 메시지(친구톡 포함)에 심으므로,\n * 친구톡까지 대체발송하려면 `['ATA', 'CTA', 'CTI']` 로 넓혀야 한다.\n * 넓히면 그만큼 조회가 늘어난다.\n */\nexport const DEFAULT_SWEEP_TYPES = ['ATA'] as const satisfies readonly SweepMessageType[];\n\n/**\n * 발송 실패 코드. 이것만 실패로 본다.\n *\n * \"성공·진행중이 아니면 실패\" 로 뒤집어 쓰면 벤더가 새 정보성 코드를 추가하는 순간\n * 그 코드를 단 모든 건이 '대체발송 못 함' 으로 사람을 깨운다. 모르는 값은 가만히 둔다.\n */\nconst DELIVERY_FAILURE = /^3\\d{3}$/;\n/** 이통사 접수 — 아직 결과가 아니다 */\nconst IN_FLIGHT_CODE = '3000';\n\nconst DEFAULT_LOOKBACK_MS = 60 * 60 * 1000;\nconst PAGE_SIZE = 200;\nconst MAX_PAGES = 50;\n\n/** 다음 스윕이 어디서부터 볼지. 프로세스 밖에 저장할 수 있게 직렬화 가능한 값만 담는다 */\nexport interface SweepCursor {\n /** 마지막으로 본 갱신 시각 (ISO 8601) */\n updatedAt: string;\n /** 그 시각과 같은 messageId 들. 경계에서 같은 건을 두 번 집지 않기 위해 */\n seen: readonly string[];\n}\n\nexport interface SweepFallbackEvent {\n messageId: string;\n to: string;\n statusCode: string;\n source: FallbackSource;\n text: string;\n}\n\nexport type SweepBlockedEvent = {\n messageId: string;\n to: string;\n statusCode: string;\n} & (\n | Extract<FallbackText, { ok: false }>\n /** 실패했지만 대체발송 대상 코드가 아니다 — 설정 오류·야간 등 */\n | { ok: false; reason: 'code_not_eligible' }\n /** 문구는 만들었는데 ClawOps 가 거절했다. 다음 스윕이 다시 시도한다 */\n | { ok: false; reason: 'send_rejected' }\n);\n\nexport interface SweepOptions {\n clawops: ClawOps;\n solapi: SolapiMessageService;\n /** 대체발송에 쓸 기본 발신번호. 조회 결과의 from 은 솔라피 발신번호라 쓰지 않는다 */\n from: string;\n /** 없으면 `lookbackMs` 만큼 거슬러 본다 */\n cursor?: SweepCursor;\n lookbackMs?: number;\n /** 대체발송할 상태코드. 기본 `DEFAULT_FALLBACK_CODES` */\n on?: readonly string[];\n /** 훑을 메시지 타입. 기본 `DEFAULT_SWEEP_TYPES` (알림톡만) */\n types?: readonly SweepMessageType[];\n fallbackField?: string;\n concurrency?: number;\n /** 이미 처리한 건을 건너뛴다. 커서가 못 막는 재갱신을 여기서 막는다 */\n skip?: (messageId: string) => boolean;\n /** 템플릿 본문 캐시. 스윕 간 재사용하면 조회가 줄어든다 */\n templateCache?: Map<string, Promise<string | undefined>>;\n onFallback?: (event: SweepFallbackEvent) => void;\n onBlocked?: (event: SweepBlockedEvent) => void;\n}\n\nexport interface SweepResult {\n /** 다음 호출에 그대로 넘긴다 */\n cursor: SweepCursor;\n /** 조회한 총 건수 */\n scanned: number;\n /** 대체발송한 건수 */\n sent: number;\n /** 문구를 만들지 못해 보내지 못한 건수 */\n blocked: number;\n /**\n * 페이지 상한에 걸려 구간을 다 못 봤다. 이때는 커서를 옮기지 않아 다음 스윕이 같은 곳을\n * 다시 본다 — 물량이 계속 상한을 넘으면 주기를 줄이거나 lookback 을 좁혀야 한다\n */\n truncated: boolean;\n /** 이번에 대체발송한 messageId. 호출자가 처리 집합에 넣는다 */\n processed: string[];\n}\n\n/** 조회 API 가 실제로 받는 메시지 타입. 문자열로 두면 enum 을 벗어난 값이 런타임에야 터진다 */\ntype SweepMessageType = NonNullable<\n NonNullable<Parameters<SolapiMessageService['getMessages']>[0]>['type']\n>;\n\n/** 조회 응답 한 건. solapi 의 storedMessage 중 우리가 쓰는 필드만 */\ninterface StoredMessage extends FallbackInput {\n messageId?: string;\n statusCode?: string | null;\n to?: string | readonly string[];\n dateCreated?: string;\n dateUpdated?: string;\n}\n\ninterface GetMessagesResponse {\n messageList?: Record<string, StoredMessage>;\n nextKey?: string | null;\n}\n\nconst firstRecipient = (to: StoredMessage['to']): string | undefined =>\n Array.isArray(to) ? to[0] : (to as string | undefined);\n\nconst timeOf = (row: StoredMessage): string | undefined => row.dateUpdated ?? row.dateCreated;\n\n/**\n * 알림톡 발송 실패(3XXX)를 찾아 문자로 대체발송한다.\n *\n * 접수 실패(1XXX)는 `send()` 응답에서 바로 잡히지만, 수신자가 카카오톡을 쓰지 않거나\n * 알림톡을 차단한 경우(3104·3107)는 접수가 성공하고 **이통사 리포트에서만 판명된다.**\n * 그래서 주기적으로 되짚어야 한다.\n *\n * 발송 시 심은 마커가 있는 건만 대상이라, 고객이 솔라피로 직접 보낸 알림톡은 건드리지 않는다.\n */\nexport async function sweepFailedAlimtalk(options: SweepOptions): Promise<SweepResult> {\n const targets = new Set<string>(options.on ?? DEFAULT_FALLBACK_CODES);\n const boundary = new Set(options.cursor?.seen ?? []);\n const templateCache = options.templateCache ?? new Map<string, Promise<string | undefined>>();\n\n const window = options.lookbackMs ?? DEFAULT_LOOKBACK_MS;\n const startDate = options.cursor\n ? new Date(options.cursor.updatedAt)\n : new Date(Date.now() - window);\n // 창을 한 번에 보는 폭으로 제한한다.\n // 상한에 걸려 커서를 유지할 때 endDate 만 계속 전진하면 창이 넓어지기만 해\n // 행 수가 영영 줄지 않는다 — 매 주기 50회씩 조회하며 진전이 0 인 상태가 된다\n const endDate = new Date(Math.min(Date.now(), startDate.getTime() + window));\n\n const candidates: StoredMessage[] = [];\n const ineligible: StoredMessage[] = [];\n let scanned = 0;\n let maxTime = startDate.getTime();\n // Set 이어야 한다 — 배열이면 경계에 걸린 id 를 매 스윕 다시 넣어 커서가 계속 부푼다\n let atMax = new Set(boundary);\n let truncated = false;\n\n for (const type of options.types ?? DEFAULT_SWEEP_TYPES) {\n let startKey: string | undefined;\n\n for (let page = 0; page < MAX_PAGES; page += 1) {\n const response = (await options.solapi.getMessages({\n type,\n dateType: 'UPDATED',\n startDate,\n endDate,\n limit: PAGE_SIZE,\n startKey,\n })) as GetMessagesResponse;\n\n const rows = Object.values(response.messageList ?? {});\n if (rows.length === 0) break;\n scanned += rows.length;\n\n for (const row of rows) {\n // 커서는 대상 여부와 무관하게 조회한 전체 기준으로 옮긴다.\n // 후보만 기준으로 삼으면 실패가 없는 구간에서 커서가 제자리에 멈춘다\n const at = timeOf(row);\n const id = row.messageId;\n if (at && id) {\n const time = new Date(at).getTime();\n if (time > maxTime) {\n maxTime = time;\n atMax = new Set([id]);\n } else if (time === maxTime) {\n atMax.add(id);\n }\n }\n\n if (!id || boundary.has(id)) continue;\n if (options.skip?.(id)) continue;\n if (row.customFields?.[FALLBACK_MARKER_FIELD] !== FALLBACK_MARKER_VALUE) continue;\n\n // 3XXX 가 아니면 아직 결과가 아니거나 우리가 모르는 상태다. 다음 스윕에서 다시 본다\n const code = String(row.statusCode ?? '');\n if (code === IN_FLIGHT_CODE || !DELIVERY_FAILURE.test(code)) continue;\n\n // 실패했는데 대체발송 대상이 아니면 조용히 넘기지 않는다.\n // auto 를 켠 고객은 \"실패하면 문자가 간다\"고 믿고 있고, 3105(미등록 템플릿) 같은\n // 설정 오류로 안 나갔다는 사실은 알아야 고칠 수 있다\n if (!targets.has(code)) {\n ineligible.push(row);\n continue;\n }\n candidates.push(row);\n }\n\n startKey = response.nextKey ?? undefined;\n if (!startKey) break;\n // 상한에 걸렸는데 아직 남았다 — 구간을 다 못 봤으므로 커서를 옮기면 안 된다\n if (page === MAX_PAGES - 1) truncated = true;\n }\n }\n\n const deliveries = await mapWithConcurrency(\n candidates,\n options.concurrency ?? DEFAULT_CONCURRENCY,\n async (row) => {\n const to = firstRecipient(row.to);\n if (to === undefined) return undefined;\n // 같은 알림톡 건은 몇 번을 다시 훑어도 문자가 두 번 나가지 않는다.\n // 커서가 비는 순간(재시작·경계)의 안전망이다.\n return deliverFallback(\n { source: row, to, from: options.from, idempotencyKey: `solapi:${row.messageId}` },\n options.solapi,\n { clawops: options.clawops, field: options.fallbackField, cache: templateCache },\n );\n },\n );\n\n // 콜백은 조회 순서대로 부른다 — 동시 실행 중에 부르면 순서가 뒤섞인다\n const processed: string[] = [];\n let blocked = 0;\n\n for (const row of ineligible) {\n blocked += 1;\n options.onBlocked?.({\n messageId: row.messageId ?? '',\n to: firstRecipient(row.to) ?? '',\n statusCode: String(row.statusCode ?? ''),\n ok: false,\n reason: 'code_not_eligible',\n });\n }\n\n deliveries.forEach((delivery, index) => {\n const row = candidates[index]!;\n const messageId = row.messageId ?? '';\n const to = firstRecipient(row.to) ?? '';\n const statusCode = String(row.statusCode ?? '');\n\n if (delivery === undefined) return;\n if (delivery.status !== 'sent') {\n blocked += 1;\n options.onBlocked?.(\n delivery.status === 'blocked'\n ? { messageId, to, statusCode, ...delivery.reason }\n : // 문구는 만들었는데 ClawOps 가 거절했다. 다음 스윕이 다시 시도하고,\n // 멱등키가 같아 중복 발송은 되지 않는다\n { messageId, to, statusCode, ok: false, reason: 'send_rejected' },\n );\n return;\n }\n processed.push(messageId);\n options.onFallback?.({\n messageId,\n to,\n statusCode,\n source: delivery.source,\n text: delivery.text,\n });\n });\n\n // 잘렸으면 커서를 그대로 둔다. 옮기면 못 본 구간을 영영 다시 안 본다\n const cursor: SweepCursor = truncated\n ? (options.cursor ?? { updatedAt: startDate.toISOString(), seen: [] })\n : { updatedAt: new Date(maxTime).toISOString(), seen: [...atMax] };\n\n return { cursor, scanned, sent: processed.length, blocked, truncated, processed };\n}\n","import type {\n SolapiMessageService,\n RequestSendMessagesSchema,\n SendRequestConfigSchema,\n DetailGroupMessageResponse,\n} from 'solapi';\nimport type { ClawOps } from '../client.js';\nimport { mapWithConcurrency, DEFAULT_CONCURRENCY } from './_concurrency.js';\nimport { clawopsType, normalizePhone, upperType } from './_message-type.js';\nimport { deliverFallback } from './_fallback.js';\nimport { SolapiBridgeError } from '../error.js';\nimport { type FallbackSource, type FallbackText, type SolapiMessage } from './fallback-text.js';\nimport {\n sweepFailedAlimtalk,\n FALLBACK_MARKER_FIELD,\n FALLBACK_MARKER_VALUE,\n type SweepCursor,\n type SweepOptions,\n} from './sweep.js';\n\nexport {\n resolveFallbackText,\n DEFAULT_FALLBACK_FIELD,\n type FallbackSource,\n type FallbackText,\n type SolapiMessage,\n} from './fallback-text.js';\nexport { SolapiBridgeError } from '../error.js';\nexport {\n sweepFailedAlimtalk,\n DEFAULT_FALLBACK_CODES,\n FALLBACK_MARKER_FIELD,\n type SweepCursor,\n type SweepOptions,\n type SweepResult,\n} from './sweep.js';\n\ntype GroupInfo = DetailGroupMessageResponse['groupInfo'];\ntype Sent = NonNullable<DetailGroupMessageResponse['messageList']>[number];\ntype Failed = DetailGroupMessageResponse['failedMessageList'][number];\n\n/** ClawOps 에 대응 개념이 없는 솔라피 집계 필드 */\nconst ZERO_CASH = { requested: 0, replacement: 0, refund: 0, sum: 0 } as const;\nconst CHARGE_KEYS = [\n 'sms',\n 'lms',\n 'mms',\n 'ata',\n 'cta',\n 'cti',\n 'nsa',\n 'rcs_sms',\n 'rcs_lms',\n 'rcs_mms',\n 'rcs_tpl',\n] as const;\nconst NO_CHARGE = Object.fromEntries(\n CHARGE_KEYS.map((k) => [k, {}]),\n) as GroupInfo['countForCharge'];\nconst NO_PROFIT = Object.fromEntries(CHARGE_KEYS.map((k) => [k, 0])) as GroupInfo['app']['profit'];\n\n/** ClawOps 가 보내는 메시지 타입 */\nconst OURS = new Set(['SMS', 'LMS', 'MMS']);\n\n/**\n * 벤더 전용 옵션. 하나라도 있으면 우리 몫이 아니다.\n *\n * solapi 의 `type` 은 optional 이고 서버가 옵션으로 종류를 추론한다.\n * `type` 만 보고 가르면 `send({ to, from, kakaoOptions })` 같은 흔한 알림톡이\n * 문자 경로로 새어 빈 본문으로 나간다.\n */\nconst VENDOR_OPTION_FIELDS = [\n 'kakaoOptions',\n 'rcsOptions',\n 'naverOptions',\n 'voiceOptions',\n 'faxOptions',\n] as const;\n\nconst hasVendorOptions = (message: SolapiMessage): boolean =>\n VENDOR_OPTION_FIELDS.some((field) => message[field] != null);\n\nconst recipientsOf = (to: string | readonly string[]): readonly string[] =>\n Array.isArray(to) ? to : [to as string];\n\n/** 우리가 접수하지 못한 건임을 알리는 코드. 솔라피 코드 체계와 겹치지 않는다 */\nexport const CLAWOPS_FAILURE_STATUS_CODE = 'CLAWOPS';\n\nexport interface FallbackEvent {\n to: string;\n source: FallbackSource;\n text: string;\n /** 스윕에서 온 경우에만 채워진다 */\n messageId?: string;\n statusCode?: string;\n}\n\nexport type FallbackBlockedEvent = {\n to: string;\n messageId?: string;\n statusCode?: string;\n} & (\n | Extract<FallbackText, { ok: false }>\n /** 스윕에서만 — 실패했지만 대체발송 대상 코드가 아니다 */\n | { ok: false; reason: 'code_not_eligible' }\n /** 스윕에서만 — 문구는 만들었는데 ClawOps 가 거절했다 */\n | { ok: false; reason: 'send_rejected' }\n);\n\n/**\n * 발송 실패 추적 방식.\n *\n * `polling`·`webhook` 은 일부러 넣지 않았다 — 유니온에 멤버를 추가하는 것은 하위호환이라\n * 필요해지면 그때 붙이면 되고, 미리 선언하면 지키지 못할 약속이 된다.\n */\nexport type FallbackTracking =\n /**\n * 접수 실패(1XXX)만 대체발송. `send()` 응답으로 즉시 판정된다.\n * 추적 옵션은 받지 않는다 — `mode` 없이 `lookbackMs` 를 써 놓고 스윕이 돈다고 오해하는 것을 막는다\n */\n | {\n mode?: undefined;\n intervalMs?: never;\n lookbackMs?: never;\n on?: never;\n initialCursor?: never;\n onCursor?: never;\n onError?: never;\n }\n /**\n * 리포트를 주기적으로 훑어 발송 실패(3XXX)까지 대체발송한다.\n * 수신자가 카카오톡을 쓰지 않거나 알림톡을 차단한 경우가 여기 해당한다.\n */\n | {\n mode: 'sweep';\n /** 스윕 주기. 기본 5분 */\n intervalMs?: number;\n /** 커서가 없을 때 거슬러 볼 구간. 기본 1시간 */\n lookbackMs?: number;\n /** 대체발송할 상태코드. 기본 3104·3107·3102 */\n on?: readonly string[];\n /** 훑을 메시지 타입. 기본은 알림톡만 — 친구톡까지 보려면 넓힌다 */\n types?: SweepOptions['types'];\n /** 프로세스 밖에 커서를 저장했다가 넘길 때 */\n initialCursor?: SweepCursor;\n onCursor?: (cursor: SweepCursor) => void;\n /** 스윕 자체가 실패했을 때. 지정하지 않으면 조용히 다음 주기에 재시도한다 */\n onError?: (error: unknown) => void;\n };\n\nexport type FallbackConfig =\n | boolean\n /**\n * 끈 상태. 다른 옵션은 받지 않는다 —\n * `{ enabled: false, mode: 'sweep' }` 처럼 써 놓고 스윕이 돈다고 오해하는 것을 막는다\n */\n | {\n enabled: false;\n mode?: never;\n field?: never;\n onFallback?: never;\n onBlocked?: never;\n }\n | ({\n enabled: true;\n /** 대체 문구를 읽어올 customFields 키 */\n field?: string;\n onFallback?: (event: FallbackEvent) => void;\n onBlocked?: (event: FallbackBlockedEvent) => void;\n } & FallbackTracking);\n\n/** 켜진 설정. 사용자가 준 모양 그대로라 필드를 옮겨 담을 필요가 없다 */\ntype EnabledFallback = Extract<FallbackConfig, { enabled: true }>;\n\n/** 꺼져 있으면 undefined. 켜져 있으면 원래 유니온 arm 을 그대로 돌려준다 */\nfunction normalizeFallback(config: FallbackConfig | undefined): EnabledFallback | undefined {\n if (config === undefined || config === true) return { enabled: true };\n if (config === false || !config.enabled) return undefined;\n return config;\n}\n\nexport interface ClawOpsMessageServiceOptions {\n /** ClawOps 클라이언트 */\n clawops: ClawOps;\n /** 문자 기본 발신번호. ClawOps 에 등록된 번호여야 한다 */\n from: string;\n /** 알림톡·RCS 등을 계속 쓸 때 넘긴다. 없으면 문자 전용 모드 */\n solapi?: SolapiMessageService;\n /**\n * 알림톡 실패 시 문자로 대체발송.\n * 기본은 접수 실패(1XXX)만 — 발송 실패(3XXX)까지 잡으려면 `mode: 'sweep'` 을 켠다.\n */\n fallback?: FallbackConfig;\n /** 동시에 띄우는 ClawOps 요청 수. 기본 10 */\n concurrency?: number;\n}\n\n/** solapi 는 send 만 노출하고 나머지는 감춘 문자 전용 서비스 */\nexport type ClawOpsTextOnlyService = Pick<SolapiMessageService, 'send'>;\n\n/**\n * 전건이 접수 실패하면 solapi SDK 는 결과 대신 MessageNotReceivedError 를 던진다.\n * 예외지만 정상 경로이고, 안에 실패 목록이 들어 있다.\n */\nfunction isMessageNotReceived(error: unknown): error is { failedMessageList: Failed[] } {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'failedMessageList' in error &&\n Array.isArray((error as { failedMessageList: unknown }).failedMessageList)\n );\n}\n\n/**\n * ClawOps 로 가는 메시지가 있는데 우리가 지킬 수 없는 발송 설정이 오면 막는다.\n * 조용히 버리면 예약 발송이 즉시 발송으로 바뀐다.\n */\nfunction assertConfigSupported(config: SendRequestConfigSchema | undefined): void {\n if (!config) return;\n const unsupported: string[] = [];\n if (config.scheduledDate !== undefined) unsupported.push('scheduledDate');\n if (config.allowDuplicates === false) unsupported.push('allowDuplicates: false');\n if (unsupported.length > 0) {\n throw new SolapiBridgeError(\n `ClawOps 로 보내는 메시지에는 ${unsupported.join(', ')} 를 적용할 수 없습니다. ` +\n '해당 설정이 필요한 발송은 솔라피로 보내거나 호출을 분리하십시오.',\n );\n }\n}\n\nfunction makeGroupInfo(\n base: GroupInfo | null,\n counts: { ok: number; failed: number; replacement: number },\n): GroupInfo {\n if (base) {\n // 솔라피 집계에 **우리 몫을 더한다**. 덮어쓰면 안 된다 —\n // `messageList` 는 `showMessageList: true` 일 때만 오므로 기본 호출에서는 비어 있고,\n // 우리가 센 값으로 덮으면 알림톡 물량이 통째로 0 이 된다.\n // 대체발송분은 솔라피가 이미 실패로 세어 놨으므로 total 에 더하지 않고\n // sentReplacement 로만 올린다 — 솔라피와 같은 의미다\n return {\n ...base,\n count: {\n ...base.count,\n total: base.count.total + counts.ok + counts.failed,\n registeredSuccess: base.count.registeredSuccess + counts.ok,\n registeredFailed: base.count.registeredFailed + counts.failed,\n sentReplacement: base.count.sentReplacement + counts.replacement,\n },\n };\n }\n\n const registered = {\n total: counts.ok + counts.failed,\n registeredSuccess: counts.ok,\n registeredFailed: counts.failed,\n };\n\n const now = new Date().toISOString();\n return {\n count: {\n ...registered,\n sentTotal: 0,\n sentFailed: 0,\n sentSuccess: 0,\n sentPending: 0,\n sentReplacement: counts.replacement,\n refund: 0,\n },\n countForCharge: NO_CHARGE,\n balance: ZERO_CASH,\n point: ZERO_CASH,\n app: { profit: NO_PROFIT, appId: null },\n log: [],\n status: 'PENDING',\n allowDuplicates: false,\n isRefunded: false,\n accountId: '',\n masterAccountId: null,\n apiVersion: '4',\n // 솔라피 그룹이 아니므로 getGroupMessages 로 조회되지 않는다. 접두사로 구분한다\n groupId: `CLAWOPS-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`,\n price: {},\n dateCreated: now,\n dateUpdated: now,\n scheduledDate: null,\n dateSent: null,\n dateCompleted: null,\n };\n}\n\n/** 어디로 보낼지 가른다. 우리가 못 보내는 첨부는 조용히 버리지 않고 여기서 막는다 */\nfunction partition(list: readonly SolapiMessage[]): {\n forSolapi: SolapiMessage[];\n forClawOps: Array<{ to: string; message: SolapiMessage }>;\n} {\n const forSolapi: SolapiMessage[] = [];\n const forClawOps: Array<{ to: string; message: SolapiMessage }> = [];\n\n for (const message of list) {\n const type = upperType(message);\n if ((type !== '' && !OURS.has(type)) || hasVendorOptions(message)) {\n forSolapi.push(message);\n continue;\n }\n if (message.imageId !== undefined) {\n throw new SolapiBridgeError(\n 'imageId 는 솔라피에 업로드된 파일 ID 라 ClawOps 로 전달할 수 없습니다. ' +\n '이미지 첨부가 필요하면 ClawOps 의 mediaUrl 로 직접 발송하십시오.',\n );\n }\n for (const to of recipientsOf(message.to)) forClawOps.push({ to, message });\n }\n return { forSolapi, forClawOps };\n}\n\ninterface Plan {\n outgoing: SolapiMessage;\n source: SolapiMessage;\n}\n\n/**\n * 대체발송에 쓸 발신번호. 없으면 대체발송하지 않는다는 뜻이다.\n *\n * 솔라피 규칙 그대로 — `from` 이 있고 `disableSms` 가 true 가 아니면 \"실패 시 문자로\".\n * 이 판정이 두 곳에 있으면 갈린다. 실제로 예약발송 검증과 계획 수립이 다르게 답한 적이 있다.\n */\nfunction fallbackFrom(message: SolapiMessage | undefined): string | undefined {\n const kakaoOptions = message?.kakaoOptions;\n if (!kakaoOptions || kakaoOptions.disableSms === true) return undefined;\n return message?.from;\n}\n\n/** ClawOps 가 거절한 건을 솔라피 실패 레코드 모양으로 옮긴다 */\nfunction clawopsFailure(to: string, from: string, error: unknown): Failed {\n return {\n to: normalizePhone(to),\n from: normalizePhone(from),\n type: 'SMS',\n statusMessage: error instanceof Error ? error.message : String(error),\n country: '82',\n messageId: '',\n statusCode: CLAWOPS_FAILURE_STATUS_CODE,\n accountId: '',\n };\n}\n\n/**\n * 솔라피 의미를 그대로 읽는다.\n * from 있음 + disableSms !== true → \"실패하면 문자로 대체발송해 달라\"\n * 실행 주체만 우리로 바뀌므로 카카오 옵션이 붙은 메시지에 한해\n * ① from 을 뺀다 — 솔라피에 미등록된 번호면 알림톡 자체가 접수 거부된다\n * ② disableSms 를 true 로 한다 — 솔라피가 중복으로 문자를 쏘지 않도록\n * RCS·NSA·FAX·VOICE 는 각자 발신번호와 옵션 체계가 따로 있어 손대지 않는다.\n */\nfunction buildPlans(messages: readonly SolapiMessage[], fallbackEnabled: boolean): Plan[] {\n return messages.map((message) => {\n const kakaoOptions = message.kakaoOptions;\n // 손댈 수 없거나 손대면 안 되는 경우는 요청을 그대로 넘긴다.\n // from 을 빼고 disableSms 를 켜 놓고 우리도 안 보내면, 고객이 원래 쓰던\n // 솔라피 자체 대체발송까지 죽는다 — 있던 기능을 잃는 쪽이 제일 나쁘다\n //\n // · fallback 을 껐다\n // · 브랜드메시지(kakaoOptions.bms) — 같은 필드를 쓰는 다른 제품이라 대체발송 규칙이 다르다\n // · customFields 가 10개(상태코드 1035)를 다 써서 마커를 심을 자리가 없다\n if (!kakaoOptions || !fallbackEnabled || kakaoOptions.bms) {\n return { outgoing: message, source: message };\n }\n const fields = message.customFields ?? {};\n const canMark = FALLBACK_MARKER_FIELD in fields || Object.keys(fields).length < 10;\n if (!canMark) return { outgoing: message, source: message };\n\n const { from: _from, ...rest } = message;\n\n // 스윕은 계정 전체를 훑으므로, 우리가 보낸 건임을 여기서 표시해 둔다.\n // 이 마커가 없으면 고객이 솔라피로 직접 보낸 알림톡까지 문자를 쏘게 된다.\n // 문구는 심지 않는다 — 발송 경로에 템플릿 조회를 넣으면 그만큼 느려진다.\n // 스윕이 조회한 자리에서 복원한다.\n //\n // mode 와 무관하게 심는다. 크론에서 sweepFailedAlimtalk() 를 부르는 구성도\n // 이 마커에 의존하는데, mode 로 게이트하면 그쪽이 조용히 0건만 훑는다\n let customFields = message.customFields;\n if (fallbackFrom(message) !== undefined) {\n customFields = { ...fields, [FALLBACK_MARKER_FIELD]: FALLBACK_MARKER_VALUE };\n }\n\n return {\n outgoing: {\n ...rest,\n ...(customFields ? { customFields } : {}),\n kakaoOptions: { ...kakaoOptions, disableSms: true },\n },\n source: message,\n };\n });\n}\n\n/**\n * 실패 건을 요청 계획에 되짚는다.\n * 같은 수신자에게 여러 건을 보낼 수 있으므로 수신자당 계획을 줄 세워 하나씩 소비하고,\n * solapi 가 하이픈을 지운 번호로 돌려주므로 양쪽 모두 정규화해서 비교한다.\n */\nfunction matchFailures(\n plans: readonly Plan[],\n failures: readonly Failed[],\n): Array<{ failure: Failed; plan?: Plan }> {\n const byRecipient = new Map<string, number[]>();\n plans.forEach((plan, index) => {\n for (const to of recipientsOf(plan.outgoing.to)) {\n const key = normalizePhone(to);\n const bucket = byRecipient.get(key);\n if (bucket) bucket.push(index);\n else byRecipient.set(key, [index]);\n }\n });\n\n return failures.map((failure) => {\n const index = byRecipient.get(normalizePhone(failure.to))?.shift();\n return { failure, plan: index === undefined ? undefined : plans[index] };\n });\n}\n\nfunction makeSend(\n options: ClawOpsMessageServiceOptions,\n fallbackConfig: EnabledFallback | undefined,\n templateCache: Map<string, Promise<string | undefined>>,\n): SolapiMessageService['send'] {\n const fallbackEnabled = fallbackConfig !== undefined;\n const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;\n\n /** 한 건을 ClawOps 로 보낸다. 실패해도 던지지 않고 결과로 알린다 */\n const viaClawOps = async (\n to: string,\n message: { from?: string; text?: string; subject?: string; type?: unknown },\n statusMessage = '정상 접수',\n ): Promise<{ ok: true; sent: Sent } | { ok: false; failed: Failed }> => {\n const from = normalizePhone(message.from ?? options.from);\n const type = clawopsType(message);\n try {\n const created = await options.clawops.messages.create({\n to: normalizePhone(to),\n from,\n body: message.text ?? '',\n type,\n subject: message.subject,\n });\n return {\n ok: true,\n sent: { messageId: created.messageId, statusCode: '2000', statusMessage },\n };\n } catch (error) {\n // 한 건이 막혀도 이미 보낸 건들의 기록을 잃지 않는다\n return {\n ok: false,\n failed: {\n to: normalizePhone(to),\n from,\n type: type.toUpperCase(),\n statusMessage: error instanceof Error ? error.message : String(error),\n country: '82',\n messageId: '',\n statusCode: CLAWOPS_FAILURE_STATUS_CODE,\n accountId: '',\n },\n };\n }\n };\n\n /** 알림톡 실패분을 문자로 대체발송한다. 문구 조회와 발송이 건마다 왕복이라 동시에 돌린다 */\n const runFallbacks = async (\n plans: readonly Plan[],\n failures: readonly Failed[],\n solapi: SolapiMessageService,\n ): Promise<{ sent: Sent[]; failed: Failed[]; replacement: number; failedCount: number }> => {\n const jobs = matchFailures(plans, failures);\n\n const deliveries = await mapWithConcurrency(jobs, concurrency, async (job) => {\n const from = fallbackFrom(job.plan?.source);\n if (from === undefined) return undefined;\n return deliverFallback({ source: job.plan!.source, to: job.failure.to, from }, solapi, {\n clawops: options.clawops,\n field: fallbackConfig?.field,\n cache: templateCache,\n });\n });\n\n // 콜백은 요청 순서대로 부른다 — 동시 실행 중에 부르면 순서가 뒤섞인다\n const sent: Sent[] = [];\n const failed: Failed[] = [];\n let replacement = 0;\n /** 대체발송을 시도했다가 ClawOps 가 거절한 건. 솔라피 실패는 이미 저쪽이 세므로 뺀다 */\n let failedCount = 0;\n\n deliveries.forEach((delivery, index) => {\n const { failure } = jobs[index]!;\n if (delivery === undefined) {\n failed.push(failure);\n return;\n }\n if (delivery.status === 'blocked') {\n fallbackConfig?.onBlocked?.({ to: failure.to, ...delivery.reason });\n failed.push(failure);\n return;\n }\n if (delivery.status === 'send_failed') {\n // 발송이 성공한 뒤에만 알린다. 먼저 부르면 ClawOps 가 거절한 건까지\n // '문자로 대체발송' 로그가 남아, 운영자가 도달했다고 믿는다\n failed.push(clawopsFailure(failure.to, options.from, delivery.error));\n failedCount += 1;\n return;\n }\n sent.push({\n messageId: delivery.messageId,\n statusCode: '2000',\n statusMessage: '정상 접수(대체발송)',\n });\n replacement += 1;\n fallbackConfig?.onFallback?.({\n to: failure.to,\n source: delivery.source,\n text: delivery.text,\n });\n });\n return { sent, failed, replacement, failedCount };\n };\n\n return async (messages, config) => {\n // 읽기만 하므로 복사하지 않는다\n const list: readonly SolapiMessage[] = Array.isArray(messages) ? messages : [messages];\n const { forSolapi, forClawOps } = partition(list);\n const sent: Sent[] = [];\n const failed: Failed[] = [];\n let replacement = 0;\n // 솔라피 집계에 더할 우리 몫. sent/failed 에는 솔라피 것도 섞여 길이로는 셀 수 없다\n let ourOk = 0;\n let ourFailed = 0;\n\n // 예약 발송처럼 우리가 못 지키는 설정은, 문자가 없어도 대체발송 대상이 있으면 막아야 한다.\n // 알림톡만 있는 배치라도 접수 실패가 나면 대체 문자가 '지금' 나가버린다\n const willFallback =\n fallbackEnabled && forSolapi.some((message) => fallbackFrom(message) !== undefined);\n if (forClawOps.length > 0 || willFallback) assertConfigSupported(config);\n\n if (forClawOps.length > 0) {\n for (const outcome of await mapWithConcurrency(forClawOps, concurrency, (item) =>\n viaClawOps(item.to, item.message),\n )) {\n if (outcome.ok) {\n sent.push(outcome.sent);\n ourOk += 1;\n } else {\n failed.push(outcome.failed);\n ourFailed += 1;\n }\n }\n }\n\n let base: GroupInfo | null = null;\n\n if (forSolapi.length > 0) {\n const solapi = options.solapi;\n if (!solapi) {\n throw new SolapiBridgeError(\n '알림톡·RCS 를 보내려면 solapi 인스턴스를 넘겨야 합니다. ' +\n 'new ClawOpsMessageService({ solapi: new SolapiMessageService(key, secret), … })',\n );\n }\n\n const plans = buildPlans(forSolapi, fallbackEnabled);\n let solapiFailed: readonly Failed[] = [];\n try {\n const response = await solapi.send(\n plans.map((plan) => plan.outgoing) as RequestSendMessagesSchema,\n config,\n );\n base = response.groupInfo;\n sent.push(...(response.messageList ?? []));\n solapiFailed = response.failedMessageList;\n } catch (error) {\n if (!isMessageNotReceived(error)) throw error;\n solapiFailed = error.failedMessageList; // 전건 실패 — groupInfo 는 오지 않는다\n }\n\n if (solapiFailed.length > 0) {\n if (!fallbackEnabled) {\n failed.push(...solapiFailed);\n } else {\n const result = await runFallbacks(plans, solapiFailed, solapi);\n sent.push(...result.sent);\n failed.push(...result.failed);\n replacement += result.replacement;\n ourFailed += result.failedCount;\n }\n }\n }\n\n return {\n groupInfo: makeGroupInfo(base, { ok: ourOk, failed: ourFailed, replacement }),\n messageList: sent,\n failedMessageList: failed,\n };\n };\n}\n\n/**\n * 문자 전용 모드에서 Proxy 가 모든 키를 메서드로 취급하면 JS 의 기본 규약이 깨진다.\n * `then` 이 함수로 보이면 thenable 로 간주돼 async 함수에서 반환할 수조차 없다.\n */\nconst NON_METHOD_KEYS = new Set(['then', 'toJSON', 'inspect']);\n\nconst DEFAULT_SWEEP_INTERVAL_MS = 5 * 60 * 1000;\n/** 처리 집합 상한. 커서가 이미 대부분을 막으므로 이건 재갱신 대비 여유분이다 */\nconst PROCESSED_LIMIT = 10_000;\n\n/**\n * 발송 실패(3XXX)를 주기적으로 되짚는 루프.\n *\n * 타이머는 `unref()` 해서 프로세스를 붙잡지 않는다 — 이 루프가 돈다는 이유로 서버가\n * 종료되지 않는 일은 없어야 한다.\n */\nfunction startSweepLoop(\n options: ClawOpsMessageServiceOptions,\n sweep: EnabledFallback & { mode: 'sweep' },\n solapi: SolapiMessageService,\n templateCache: Map<string, Promise<string | undefined>>,\n): void {\n const processed = new Set<string>();\n let cursor = sweep.initialCursor;\n let running = false;\n\n const tick = async (): Promise<void> => {\n if (running) return; // 한 주기가 길어져도 겹쳐 돌지 않는다\n running = true;\n try {\n const result = await sweepFailedAlimtalk({\n clawops: options.clawops,\n solapi,\n from: options.from,\n cursor,\n lookbackMs: sweep.lookbackMs,\n on: sweep.on,\n types: sweep.types,\n fallbackField: sweep.field,\n concurrency: options.concurrency,\n templateCache,\n skip: (messageId) => processed.has(messageId),\n onFallback: sweep.onFallback,\n onBlocked: sweep.onBlocked,\n });\n\n cursor = result.cursor;\n for (const messageId of result.processed) processed.add(messageId);\n if (processed.size > PROCESSED_LIMIT) {\n // Set 은 삽입 순서를 지키므로 오래된 절반을 버린다\n for (const messageId of [...processed].slice(0, processed.size - PROCESSED_LIMIT / 2)) {\n processed.delete(messageId);\n }\n }\n // 빈 스윕까지 알리면 커서를 외부에 저장하는 쪽이 하루 288번 같은 값을 다시 쓴다\n if (result.scanned > 0) sweep.onCursor?.(result.cursor);\n } catch (error) {\n // 스윕 실패가 프로세스를 죽이지 않는다. 커서를 옮기지 않았으므로 다음 주기가 같은 구간을 다시 본다\n sweep.onError?.(error);\n } finally {\n running = false;\n }\n };\n\n const timer = setInterval(() => void tick(), sweep.intervalMs ?? DEFAULT_SWEEP_INTERVAL_MS);\n timer.unref?.();\n}\n\nfunction create(options: ClawOpsMessageServiceOptions): SolapiMessageService {\n const fallbackConfig = normalizeFallback(options.fallback);\n const templateCache = new Map<string, Promise<string | undefined>>();\n const send = makeSend(options, fallbackConfig, templateCache);\n\n if (fallbackConfig?.mode === 'sweep') {\n if (!options.solapi) {\n throw new SolapiBridgeError(\n \"mode: 'sweep' 은 솔라피 리포트를 훑는 기능이라 solapi 인스턴스가 필요합니다. \" +\n '문자만 보낸다면 이 설정은 할 일이 없습니다.',\n );\n }\n startSweepLoop(options, fallbackConfig, options.solapi, templateCache);\n }\n\n // 주입된 solapi 인스턴스를 감싼다. 원본은 수정하지 않는다.\n // send 만 우리가 가로채고 나머지 메서드는 그대로 흘려보낸다.\n //\n // 핸들러는 생성 시점에 한 번 고른다 — solapi 유무는 객체 수명 동안 바뀌지 않으므로\n // 프로퍼티 접근마다 분기할 이유가 없고, 문자 전용 모드의 특수 처리도 한쪽에만 모인다\n const solapi = options.solapi;\n const handler: ProxyHandler<SolapiMessageService> = solapi\n ? {\n get(instance, property) {\n if (property === 'send') return send;\n const value = Reflect.get(instance, property, instance);\n return typeof value === 'function' ? value.bind(instance) : value;\n },\n }\n : {\n get(_instance, property) {\n if (property === 'send') return send;\n if (typeof property === 'symbol' || NON_METHOD_KEYS.has(property)) return undefined;\n // Object.prototype 의 것들은 JS 가 답하게 둔다. 막으면 hasOwnProperty 같은 것이 깨진다\n if (property in Object.prototype) {\n return Reflect.get(Object.prototype, property) as unknown;\n }\n return () => {\n throw new SolapiBridgeError(\n `${String(property)}() 는 솔라피 기능입니다. solapi 인스턴스를 넘겨주세요.`,\n );\n };\n },\n };\n\n return new Proxy((solapi ?? {}) as SolapiMessageService, handler);\n}\n\ninterface ClawOpsMessageServiceConstructor {\n /** solapi 를 넘기면 SolapiMessageService 와 완전히 동일한 타입 */\n new (\n options: ClawOpsMessageServiceOptions & { solapi: SolapiMessageService },\n ): SolapiMessageService;\n /** 안 넘기면 문자 전용. 솔라피 기능은 타입에 나타나지 않는다 */\n new (options: ClawOpsMessageServiceOptions & { solapi?: undefined }): ClawOpsTextOnlyService;\n /** 있는지 런타임에만 아는 경우. 두 경우 모두에서 안전한 문자 전용 타입을 준다 */\n new (options: ClawOpsMessageServiceOptions): ClawOpsTextOnlyService;\n}\n\n/**\n * 솔라피 코드를 그대로 두고 문자만 ClawOps 로 보낸다.\n *\n * `new` 의 결과 타입은 `SolapiMessageService` 그 자체라 기존 코드의 타입 자리에 그대로 들어간다.\n */\nexport const ClawOpsMessageService = function (\n this: unknown,\n options: ClawOpsMessageServiceOptions,\n) {\n return create(options);\n} as unknown as ClawOpsMessageServiceConstructor;\n"]}
1
+ {"version":3,"sources":["../../src/solapi/_concurrency.ts","../../src/solapi/_message-type.ts","../../src/solapi/fallback-text.ts","../../src/solapi/_fallback.ts","../../src/solapi/sweep.ts","../../src/solapi/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** 한 번에 띄우는 ClawOps 요청 수. 솔라피의 배치 한 번이 우리에겐 요청 N 번이 된다 */\nexport const DEFAULT_CONCURRENCY = 10;\n\n/**\n * 순서를 지키면서 동시 요청 수를 제한한다.\n *\n * 배치를 끊어 기다리면 한 건의 재시도가 같은 배치 전체를 붙잡으므로,\n * 워커가 공용 커서를 당겨 써서 빈 슬롯이 생기지 않게 한다.\n */\nexport async function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n run: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let cursor = 0;\n const worker = async (): Promise<void> => {\n for (let index = cursor++; index < items.length; index = cursor++) {\n results[index] = await run(items[index]!);\n }\n };\n // limit 이 0 이면 워커가 하나도 안 떠서 전부 조용히 누락된다.\n // `concurrency: Number(process.env.X)` 가 미설정 시 0 이 되는 흔한 경로가 있다\n const workers = Math.min(Math.max(1, limit), items.length);\n await Promise.all(Array.from({ length: workers }, worker));\n return results;\n}\n","/**\n * SMS 본문 상한. **EUC-KR 90byte 다** — UTF-8 이 아니다.\n *\n * 서버의 판정 기준(`app/src/services/messages.ts` 의 `SMS_MAX_EUCKR_BYTES`)과 같아야 한다.\n * 어긋나면 우리가 sms 라고 보낸 건이 서버에서 `body_too_long` 으로 거절된다.\n *\n * 솔라피의 SMS 기준(90byte EUC-KR)과 **동일하다**. 옮겨오는 코드의 분기 로직을 그대로 쓸 수 있다.\n */\nexport const SMS_MAX_EUCKR_BYTES = 90;\n\n/**\n * EUC-KR 기준 바이트 수. ASCII 1byte, 그 외 2byte.\n *\n * EUC-KR 로 표현할 수 없는 문자는 게이트웨이가 `?` 한 글자로 치환하므로 실제로는 1byte 지만\n * 2byte 로 세어 과대평가 쪽에 둔다 — 이 방향의 오차는 본문 잘림을 만들지 않는다.\n */\nexport function euckrByteLength(text: string): number {\n let bytes = 0;\n for (const ch of text) bytes += ch.codePointAt(0)! < 0x80 ? 1 : 2;\n return bytes;\n}\n\n/** solapi 는 phoneNumberSchema 로 하이픈을 제거한다. 응답과 맞추려면 같은 규칙이 필요하다 */\nexport const normalizePhone = (value: string): string => value.replace(/-/g, '');\n\n/**\n * ClawOps 로 보낼 때의 메시지 타입.\n * 명시된 sms/lms 는 그대로 존중하고, 그 외에는 서버와 같은 규칙으로 정한다.\n */\n/** 메시지 타입을 한 가지 표기로 통일한다. 없으면 빈 문자열 */\nexport const upperType = (message: { type?: unknown }): string =>\n message.type === undefined ? '' : String(message.type).toUpperCase();\n\nexport function clawopsType(message: {\n type?: unknown;\n text?: string | null;\n subject?: string | null;\n}): 'sms' | 'lms' {\n const type = upperType(message);\n if (type === 'SMS') return 'sms';\n if (type === 'LMS') return 'lms';\n if (message.subject !== undefined && message.subject !== null && message.subject !== '') {\n return 'lms';\n }\n return euckrByteLength(message.text ?? '') > SMS_MAX_EUCKR_BYTES ? 'lms' : 'sms';\n}\n","import type { SolapiMessageService, RequestSendMessagesSchema } from 'solapi';\n\n/** 제네릭을 거쳐야 유니온(단건 | 배열)에 분배된다 */\ntype Elem<T> = T extends readonly (infer U)[] ? U : T;\nexport type SolapiMessage = Elem<RequestSendMessagesSchema>;\n\nconst VARIABLE_KEY = /^#\\{.+\\}$/;\nconst LEFTOVER = /#\\{[^}]*\\}/g;\n\n/** solapi SDK 와 같은 규칙: `name` → `#{name}`, 이미 `#{...}` 면 그대로 */\nconst asKey = (key: string): string => (VARIABLE_KEY.test(key) ? key : `#{${key}}`);\n\nexport function render(content: string, variables: Record<string, string> = {}): string {\n let out = content;\n for (const [key, value] of Object.entries(variables)) {\n out = out.split(asKey(key)).join(value);\n }\n return out;\n}\n\n/** 치환되지 않고 남은 변수. 하나라도 있으면 발송하지 않는다 */\nexport const leftovers = (text: string): string[] => text.match(LEFTOVER) ?? [];\n\nexport type FallbackSource = 'customFields' | 'template' | 'text';\n\n/**\n * 문구 복원에 필요한 최소 모양.\n *\n * 발송 요청(`SolapiMessage`)과 조회 결과(`storedMessage`)를 둘 다 받는다 —\n * 조회 결과의 `kakaoOptions` 는 서버 정규화 포맷이라 `Record<string, unknown>` 으로 온다.\n */\nexport interface FallbackInput {\n type?: unknown;\n text?: string | null;\n customFields?: Record<string, string> | null;\n kakaoOptions?: Record<string, unknown> | null;\n}\n\n/** 정규화 포맷이든 요청 포맷이든 템플릿 참조를 안전하게 꺼낸다 */\nfunction templateRef(kakaoOptions: unknown): {\n templateId?: string;\n variables?: Record<string, string>;\n} {\n if (kakaoOptions === null || typeof kakaoOptions !== 'object') return {};\n const options = kakaoOptions as Record<string, unknown>;\n const templateId = typeof options.templateId === 'string' ? options.templateId : undefined;\n\n let variables: Record<string, string> | undefined;\n const raw = options.variables;\n if (raw !== null && typeof raw === 'object') {\n const entries = Object.entries(raw as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n );\n if (entries.length > 0) variables = Object.fromEntries(entries);\n }\n return { templateId, variables };\n}\n\nexport type FallbackText =\n | { ok: true; text: string; source: FallbackSource }\n | { ok: false; reason: 'unresolved_variables'; unresolved: string[]; source: FallbackSource }\n | { ok: false; reason: 'no_template_content' | 'no_text'; source: FallbackSource };\n\nexport interface ResolveOptions {\n /** 대체 문구를 읽어올 customFields 키 */\n field?: string;\n /**\n * 템플릿 본문 캐시. 호출자가 수명을 정한다.\n *\n * 값이 아니라 진행 중인 조회를 담는다 — 여러 건이 동시에 같은 템플릿을 찾을 때\n * 완료를 기다리며 캐시가 비어 있으면 같은 요청이 그 수만큼 나간다.\n */\n cache?: Map<string, Promise<string | undefined>>;\n}\n\nexport const DEFAULT_FALLBACK_FIELD = 'clawopsFallbackText';\n\n/**\n * 대체발송에 쓸 문구를 만든다.\n *\n * 솔라피는 이 문구를 콘솔(템플릿별 대체발송 설정)에만 두고 API 로 노출하지 않는다.\n * 알림톡은 대개 `text` 없이 `variables` 만 보내므로 템플릿 본문을 조회해 직접 치환한다.\n */\nexport async function resolveFallbackText(\n message: FallbackInput,\n solapi: SolapiMessageService,\n options: ResolveOptions = {},\n): Promise<FallbackText> {\n const field = options.field ?? DEFAULT_FALLBACK_FIELD;\n\n // 고객이 명시한 문구가 있으면 그것이 정본이다\n const explicit = message.customFields?.[field];\n if (explicit !== undefined) return finish(explicit, 'customFields');\n\n // 그 외에는 `kakaoOptions.templateId` 의 **존재**가 출처를 정한다. `type` 으로 판정하면 안 된다 —\n // solapi 의 `type` 은 optional 이고 서버가 vendor 옵션으로 추론하므로, 생략한 알림톡이\n // 템플릿 경로를 건너뛰고 `no_text` 로 떨어진다(실호출로 확인).\n //\n // NSA(네이버 스마트알림)는 자연히 빠진다 — templateId 가 `naverOptions` 에 있어\n // `kakaoOptions` 만 보는 templateRef 에 걸리지 않는다. 조회 API 도 달라서 여기서 다루면 항상 실패한다.\n const { templateId, variables } = templateRef(message.kakaoOptions);\n if (templateId !== undefined) {\n const cache = options.cache;\n let pending = cache?.get(templateId);\n if (pending === undefined) {\n pending = solapi.getKakaoAlimtalkTemplate(templateId).then((template) => template.content);\n // await 하기 전에 등록해야 뒤따르는 호출이 같은 조회를 기다린다\n cache?.set(templateId, pending);\n // 실패는 캐시하지 않는다. 남겨두면 일시적 오류가 영구 실패가 된다\n pending.catch(() => cache?.delete(templateId));\n }\n const content = await pending;\n if (content === undefined) {\n return { ok: false, reason: 'no_template_content', source: 'template' };\n }\n return finish(render(content, variables), 'template');\n }\n\n if (message.text === undefined || message.text === null) {\n return { ok: false, reason: 'no_text', source: 'text' };\n }\n return finish(message.text, 'text');\n}\n\nfunction finish(text: string, source: FallbackSource): FallbackText {\n const unresolved = leftovers(text);\n return unresolved.length > 0\n ? { ok: false, reason: 'unresolved_variables', unresolved, source }\n : { ok: true, text, source };\n}\n","import type { SolapiMessageService } from 'solapi';\nimport type { ClawOps } from '../client.js';\nimport { clawopsType, normalizePhone } from './_message-type.js';\nimport {\n resolveFallbackText,\n type FallbackInput,\n type FallbackSource,\n type FallbackText,\n} from './fallback-text.js';\n\nexport type BlockedReason = Extract<FallbackText, { ok: false }>;\n\n/**\n * 대체발송 한 건의 결말.\n *\n * 접수 실패 경로(`send()` 안)와 발송 실패 경로(스윕)가 같은 일을 하므로 여기서 한 번만 정의한다.\n * 두 곳에 복사해 두면 한쪽에만 가드가 붙는 일이 실제로 벌어진다.\n */\nexport type FallbackDelivery =\n /** 문구를 만들지 못했다 */\n | { status: 'blocked'; reason: BlockedReason }\n /** 문자로 나갔다 */\n | { status: 'sent'; source: FallbackSource; text: string; messageId: string }\n /** 문구는 만들었지만 ClawOps 가 거절했다 */\n | { status: 'send_failed'; source: FallbackSource; text: string; error: unknown };\n\nexport interface FallbackContext {\n clawops: ClawOps;\n /** 대체 문구를 읽어올 customFields 키 */\n field?: string;\n /** 템플릿 본문 캐시 */\n cache: Map<string, Promise<string | undefined>>;\n}\n\nexport interface FallbackRequest {\n /** 문구를 복원할 원본. 발송 요청이든 조회된 메시지든 상관없다 */\n source: FallbackInput & { subject?: string | null };\n to: string;\n from: string;\n /** 같은 건을 다시 보내지 않도록. 스윕처럼 재실행되는 경로만 채운다 */\n idempotencyKey?: string;\n}\n\n/**\n * 문구를 복원해 문자 한 건을 보낸다.\n *\n * **두 단계 모두 이 안에서 격리한다.** 밖으로 던지면 호출자가 배치 단위로 실패하는데,\n * 그 시점엔 이미 다른 건이 나가 있어 기록을 잃고 재시도하면 중복 발송이 된다.\n * 스윕에서는 더 나쁘다 — 한 건이 던지면 커서가 안 옮겨져 다음 주기가 같은 건을 다시 만나고,\n * 대체발송이 영영 진행되지 않는다.\n */\nexport async function deliverFallback(\n request: FallbackRequest,\n solapi: SolapiMessageService,\n context: FallbackContext,\n): Promise<FallbackDelivery> {\n let resolved: FallbackText;\n try {\n resolved = await resolveFallbackText(request.source, solapi, {\n field: context.field,\n cache: context.cache,\n });\n } catch {\n resolved = { ok: false, reason: 'no_template_content', source: 'template' };\n }\n if (!resolved.ok) return { status: 'blocked', reason: resolved };\n\n const subject = request.source.subject ?? undefined;\n try {\n const created = await context.clawops.messages.create({\n to: normalizePhone(request.to),\n from: normalizePhone(request.from),\n body: resolved.text,\n type: clawopsType({ text: resolved.text, subject }),\n subject,\n idempotencyKey: request.idempotencyKey,\n });\n return {\n status: 'sent',\n source: resolved.source,\n text: resolved.text,\n messageId: created.messageId,\n };\n } catch (error) {\n return { status: 'send_failed', source: resolved.source, text: resolved.text, error };\n }\n}\n","import type { SolapiMessageService } from 'solapi';\nimport type { ClawOps } from '../client.js';\nimport { mapWithConcurrency, DEFAULT_CONCURRENCY } from './_concurrency.js';\nimport { deliverFallback } from './_fallback.js';\nimport type { FallbackInput, FallbackSource, FallbackText } from './fallback-text.js';\n\n/** 발송 시 심는 마커. 이 값이 있는 건만 대체발송 후보다 */\nexport const FALLBACK_MARKER_FIELD = 'clawopsFallback';\nexport const FALLBACK_MARKER_VALUE = '1';\n\n/**\n * 기본은 **모든 발송 실패(3XXX)** 다. `on` 을 주지 않으면 3XXX 를 전부 대체발송한다.\n *\n * 코드를 골라 담지 않는다. 알림톡이라고 31xx 만 오는 게 아니기 때문이다 — 실측에서\n * 알림톡 건에 `3058`(전송경로 없음)이 돌아왔다. 목록을 추리면 그렇게 새는 코드가\n * 조용히 미발송이 된다. 놓치는 쪽보다 보내는 쪽을 기본으로 둔다.\n *\n * 대신 **덮으면 안 되는 코드가 섞여 있다.** 아래를 `except` 로 빼는 것을 권한다.\n */\nexport const RECOMMENDED_EXCLUDED_CODES = [\n // 수신거부 — 문자로 대체하면 거부한 사람에게 보내는 것이 된다\n '3061',\n // 스팸·발신번호 변작 차단 — 막힌 발송을 문자로 우회하는 셈이 된다\n '3054',\n '3055',\n '3059',\n '3112',\n '3113',\n // 설정 오류 — 전건이 실패한다. 문자가 덮으면 알림톡이 깨진 걸 몇 달간 모르고,\n // 단가도 알림톡에서 문자로 조용히 올라간다\n '3013',\n '3101',\n '3103',\n '3105',\n '3106',\n '3109',\n '3117',\n // 발송 가능 시간이 아님 — 문자로 대체하면 야간 발송이 된다\n '3108',\n] as const;\n\n/**\n * 훑을 메시지 타입. 기본은 알림톡만이다.\n *\n * 발송 시 마커는 `kakaoOptions` 가 붙은 모든 메시지(친구톡 포함)에 심으므로,\n * 친구톡까지 대체발송하려면 `['ATA', 'CTA', 'CTI']` 로 넓혀야 한다.\n * 넓히면 그만큼 조회가 늘어난다.\n */\nexport const DEFAULT_SWEEP_TYPES = ['ATA'] as const satisfies readonly SweepMessageType[];\n\n/**\n * 발송 실패 코드. 이것만 실패로 본다.\n *\n * \"성공·진행중이 아니면 실패\" 로 뒤집어 쓰면 벤더가 새 정보성 코드를 추가하는 순간\n * 그 코드를 단 모든 건이 '대체발송 못 함' 으로 사람을 깨운다. 모르는 값은 가만히 둔다.\n */\nconst DELIVERY_FAILURE = /^3\\d{3}$/;\n/** 이통사 접수 — 아직 결과가 아니다 */\nconst IN_FLIGHT_CODE = '3000';\n\nconst DEFAULT_LOOKBACK_MS = 60 * 60 * 1000;\nconst PAGE_SIZE = 200;\nconst MAX_PAGES = 50;\n\n/** 다음 스윕이 어디서부터 볼지. 프로세스 밖에 저장할 수 있게 직렬화 가능한 값만 담는다 */\nexport interface SweepCursor {\n /** 마지막으로 본 갱신 시각 (ISO 8601) */\n updatedAt: string;\n /** 그 시각과 같은 messageId 들. 경계에서 같은 건을 두 번 집지 않기 위해 */\n seen: readonly string[];\n}\n\nexport interface SweepFallbackEvent {\n messageId: string;\n to: string;\n statusCode: string;\n source: FallbackSource;\n text: string;\n}\n\nexport type SweepBlockedEvent = {\n messageId: string;\n to: string;\n statusCode: string;\n} & (\n | Extract<FallbackText, { ok: false }>\n /** 실패했지만 대체발송 대상 코드가 아니다 — 설정 오류·야간 등 */\n | { ok: false; reason: 'code_not_eligible' }\n /** 문구는 만들었는데 ClawOps 가 거절했다. 다음 스윕이 다시 시도한다 */\n | { ok: false; reason: 'send_rejected' }\n);\n\nexport interface SweepOptions {\n clawops: ClawOps;\n solapi: SolapiMessageService;\n /** 대체발송에 쓸 기본 발신번호. 조회 결과의 from 은 솔라피 발신번호라 쓰지 않는다 */\n from: string;\n /** 없으면 `lookbackMs` 만큼 거슬러 본다 */\n cursor?: SweepCursor;\n lookbackMs?: number;\n /** 대체발송할 상태코드. 주지 않으면 **모든 3XXX** 가 대상이다 */\n on?: readonly string[];\n /**\n * 대상에서 뺄 상태코드. `on` 으로 좁힌 뒤 여기서 다시 뺀다.\n * 빼려는 코드는 `RECOMMENDED_EXCLUDED_CODES` 를 그대로 넘기면 된다.\n */\n except?: readonly string[];\n /** 훑을 메시지 타입. 기본 `DEFAULT_SWEEP_TYPES` (알림톡만) */\n types?: readonly SweepMessageType[];\n fallbackField?: string;\n concurrency?: number;\n /** 이미 처리한 건을 건너뛴다. 커서가 못 막는 재갱신을 여기서 막는다 */\n skip?: (messageId: string) => boolean;\n /** 템플릿 본문 캐시. 스윕 간 재사용하면 조회가 줄어든다 */\n templateCache?: Map<string, Promise<string | undefined>>;\n onFallback?: (event: SweepFallbackEvent) => void;\n onBlocked?: (event: SweepBlockedEvent) => void;\n}\n\nexport interface SweepResult {\n /** 다음 호출에 그대로 넘긴다 */\n cursor: SweepCursor;\n /** 조회한 총 건수 */\n scanned: number;\n /** 대체발송한 건수 */\n sent: number;\n /** 문구를 만들지 못해 보내지 못한 건수 */\n blocked: number;\n /**\n * 페이지 상한에 걸려 구간을 다 못 봤다. 이때는 커서를 옮기지 않아 다음 스윕이 같은 곳을\n * 다시 본다 — 물량이 계속 상한을 넘으면 주기를 줄이거나 lookback 을 좁혀야 한다\n */\n truncated: boolean;\n /** 이번에 대체발송한 messageId. 호출자가 처리 집합에 넣는다 */\n processed: string[];\n}\n\n/** 조회 API 가 실제로 받는 메시지 타입. 문자열로 두면 enum 을 벗어난 값이 런타임에야 터진다 */\ntype SweepMessageType = NonNullable<\n NonNullable<Parameters<SolapiMessageService['getMessages']>[0]>['type']\n>;\n\n/** 조회 응답 한 건. solapi 의 storedMessage 중 우리가 쓰는 필드만 */\ninterface StoredMessage extends FallbackInput {\n messageId?: string;\n statusCode?: string | null;\n to?: string | readonly string[];\n dateCreated?: string;\n dateUpdated?: string;\n}\n\ninterface GetMessagesResponse {\n messageList?: Record<string, StoredMessage>;\n nextKey?: string | null;\n}\n\nconst firstRecipient = (to: StoredMessage['to']): string | undefined =>\n Array.isArray(to) ? to[0] : (to as string | undefined);\n\nconst timeOf = (row: StoredMessage): string | undefined => row.dateUpdated ?? row.dateCreated;\n\n/**\n * 알림톡 발송 실패(3XXX)를 찾아 문자로 대체발송한다.\n *\n * 접수 실패(1XXX)는 `send()` 응답에서 바로 잡히지만, 수신자가 카카오톡을 쓰지 않거나\n * 알림톡을 차단한 경우(3104·3107)는 접수가 성공하고 **이통사 리포트에서만 판명된다.**\n * 그래서 주기적으로 되짚어야 한다.\n *\n * 발송 시 심은 마커가 있는 건만 대상이라, 고객이 솔라피로 직접 보낸 알림톡은 건드리지 않는다.\n */\nexport async function sweepFailedAlimtalk(options: SweepOptions): Promise<SweepResult> {\n // null 이면 3XXX 전부가 대상이다. 빈 Set 과 구별해야 한다 — `on: []` 는 '아무것도 안 함' 이다\n const targets = options.on ? new Set<string>(options.on) : null;\n const excluded = new Set<string>(options.except ?? []);\n const boundary = new Set(options.cursor?.seen ?? []);\n const templateCache = options.templateCache ?? new Map<string, Promise<string | undefined>>();\n\n const window = options.lookbackMs ?? DEFAULT_LOOKBACK_MS;\n const startDate = options.cursor\n ? new Date(options.cursor.updatedAt)\n : new Date(Date.now() - window);\n // 창을 한 번에 보는 폭으로 제한한다.\n // 상한에 걸려 커서를 유지할 때 endDate 만 계속 전진하면 창이 넓어지기만 해\n // 행 수가 영영 줄지 않는다 — 매 주기 50회씩 조회하며 진전이 0 인 상태가 된다\n const endDate = new Date(Math.min(Date.now(), startDate.getTime() + window));\n\n const candidates: StoredMessage[] = [];\n const ineligible: StoredMessage[] = [];\n let scanned = 0;\n let maxTime = startDate.getTime();\n // Set 이어야 한다 — 배열이면 경계에 걸린 id 를 매 스윕 다시 넣어 커서가 계속 부푼다\n let atMax = new Set(boundary);\n let truncated = false;\n\n for (const type of options.types ?? DEFAULT_SWEEP_TYPES) {\n let startKey: string | undefined;\n\n for (let page = 0; page < MAX_PAGES; page += 1) {\n const response = (await options.solapi.getMessages({\n type,\n dateType: 'UPDATED',\n startDate,\n endDate,\n limit: PAGE_SIZE,\n startKey,\n })) as GetMessagesResponse;\n\n const rows = Object.values(response.messageList ?? {});\n if (rows.length === 0) break;\n scanned += rows.length;\n\n for (const row of rows) {\n // 커서는 대상 여부와 무관하게 조회한 전체 기준으로 옮긴다.\n // 후보만 기준으로 삼으면 실패가 없는 구간에서 커서가 제자리에 멈춘다\n const at = timeOf(row);\n const id = row.messageId;\n if (at && id) {\n const time = new Date(at).getTime();\n if (time > maxTime) {\n maxTime = time;\n atMax = new Set([id]);\n } else if (time === maxTime) {\n atMax.add(id);\n }\n }\n\n if (!id || boundary.has(id)) continue;\n if (options.skip?.(id)) continue;\n if (row.customFields?.[FALLBACK_MARKER_FIELD] !== FALLBACK_MARKER_VALUE) continue;\n\n // 3XXX 가 아니면 아직 결과가 아니거나 우리가 모르는 상태다. 다음 스윕에서 다시 본다\n const code = String(row.statusCode ?? '');\n if (code === IN_FLIGHT_CODE || !DELIVERY_FAILURE.test(code)) continue;\n\n // 실패했는데 대체발송 대상이 아니면 조용히 넘기지 않는다.\n // auto 를 켠 고객은 \"실패하면 문자가 간다\"고 믿고 있고, 3105(미등록 템플릿) 같은\n // 설정 오류로 안 나갔다는 사실은 알아야 고칠 수 있다\n if ((targets !== null && !targets.has(code)) || excluded.has(code)) {\n ineligible.push(row);\n continue;\n }\n candidates.push(row);\n }\n\n startKey = response.nextKey ?? undefined;\n if (!startKey) break;\n // 상한에 걸렸는데 아직 남았다 — 구간을 다 못 봤으므로 커서를 옮기면 안 된다\n if (page === MAX_PAGES - 1) truncated = true;\n }\n }\n\n const deliveries = await mapWithConcurrency(\n candidates,\n options.concurrency ?? DEFAULT_CONCURRENCY,\n async (row) => {\n const to = firstRecipient(row.to);\n if (to === undefined) return undefined;\n // 같은 알림톡 건은 몇 번을 다시 훑어도 문자가 두 번 나가지 않는다.\n // 커서가 비는 순간(재시작·경계)의 안전망이다.\n return deliverFallback(\n { source: row, to, from: options.from, idempotencyKey: `solapi:${row.messageId}` },\n options.solapi,\n { clawops: options.clawops, field: options.fallbackField, cache: templateCache },\n );\n },\n );\n\n // 콜백은 조회 순서대로 부른다 — 동시 실행 중에 부르면 순서가 뒤섞인다\n const processed: string[] = [];\n let blocked = 0;\n\n for (const row of ineligible) {\n blocked += 1;\n options.onBlocked?.({\n messageId: row.messageId ?? '',\n to: firstRecipient(row.to) ?? '',\n statusCode: String(row.statusCode ?? ''),\n ok: false,\n reason: 'code_not_eligible',\n });\n }\n\n deliveries.forEach((delivery, index) => {\n const row = candidates[index]!;\n const messageId = row.messageId ?? '';\n const to = firstRecipient(row.to) ?? '';\n const statusCode = String(row.statusCode ?? '');\n\n if (delivery === undefined) return;\n if (delivery.status !== 'sent') {\n blocked += 1;\n options.onBlocked?.(\n delivery.status === 'blocked'\n ? { messageId, to, statusCode, ...delivery.reason }\n : // 문구는 만들었는데 ClawOps 가 거절했다. 다음 스윕이 다시 시도하고,\n // 멱등키가 같아 중복 발송은 되지 않는다\n { messageId, to, statusCode, ok: false, reason: 'send_rejected' },\n );\n return;\n }\n processed.push(messageId);\n options.onFallback?.({\n messageId,\n to,\n statusCode,\n source: delivery.source,\n text: delivery.text,\n });\n });\n\n // 잘렸으면 커서를 그대로 둔다. 옮기면 못 본 구간을 영영 다시 안 본다\n const cursor: SweepCursor = truncated\n ? (options.cursor ?? { updatedAt: startDate.toISOString(), seen: [] })\n : { updatedAt: new Date(maxTime).toISOString(), seen: [...atMax] };\n\n return { cursor, scanned, sent: processed.length, blocked, truncated, processed };\n}\n","import type {\n SolapiMessageService,\n RequestSendMessagesSchema,\n SendRequestConfigSchema,\n DetailGroupMessageResponse,\n} from 'solapi';\nimport type { ClawOps } from '../client.js';\nimport { mapWithConcurrency, DEFAULT_CONCURRENCY } from './_concurrency.js';\nimport { clawopsType, normalizePhone, upperType } from './_message-type.js';\nimport { deliverFallback } from './_fallback.js';\nimport { SolapiBridgeError } from '../error.js';\nimport { type FallbackSource, type FallbackText, type SolapiMessage } from './fallback-text.js';\nimport {\n sweepFailedAlimtalk,\n FALLBACK_MARKER_FIELD,\n FALLBACK_MARKER_VALUE,\n type SweepCursor,\n type SweepOptions,\n} from './sweep.js';\n\nexport {\n resolveFallbackText,\n DEFAULT_FALLBACK_FIELD,\n type FallbackSource,\n type FallbackText,\n type SolapiMessage,\n} from './fallback-text.js';\nexport { SolapiBridgeError } from '../error.js';\nexport {\n sweepFailedAlimtalk,\n RECOMMENDED_EXCLUDED_CODES,\n FALLBACK_MARKER_FIELD,\n type SweepCursor,\n type SweepOptions,\n type SweepResult,\n} from './sweep.js';\n\ntype GroupInfo = DetailGroupMessageResponse['groupInfo'];\ntype Sent = NonNullable<DetailGroupMessageResponse['messageList']>[number];\ntype Failed = DetailGroupMessageResponse['failedMessageList'][number];\n\n/** ClawOps 에 대응 개념이 없는 솔라피 집계 필드 */\nconst ZERO_CASH = { requested: 0, replacement: 0, refund: 0, sum: 0 } as const;\nconst CHARGE_KEYS = [\n 'sms',\n 'lms',\n 'mms',\n 'ata',\n 'cta',\n 'cti',\n 'nsa',\n 'rcs_sms',\n 'rcs_lms',\n 'rcs_mms',\n 'rcs_tpl',\n] as const;\nconst NO_CHARGE = Object.fromEntries(\n CHARGE_KEYS.map((k) => [k, {}]),\n) as GroupInfo['countForCharge'];\nconst NO_PROFIT = Object.fromEntries(CHARGE_KEYS.map((k) => [k, 0])) as GroupInfo['app']['profit'];\n\n/** ClawOps 가 보내는 메시지 타입 */\nconst OURS = new Set(['SMS', 'LMS', 'MMS']);\n\n/**\n * 벤더 전용 옵션. 하나라도 있으면 우리 몫이 아니다.\n *\n * solapi 의 `type` 은 optional 이고 서버가 옵션으로 종류를 추론한다.\n * `type` 만 보고 가르면 `send({ to, from, kakaoOptions })` 같은 흔한 알림톡이\n * 문자 경로로 새어 빈 본문으로 나간다.\n */\nconst VENDOR_OPTION_FIELDS = [\n 'kakaoOptions',\n 'rcsOptions',\n 'naverOptions',\n 'voiceOptions',\n 'faxOptions',\n] as const;\n\nconst hasVendorOptions = (message: SolapiMessage): boolean =>\n VENDOR_OPTION_FIELDS.some((field) => message[field] != null);\n\nconst recipientsOf = (to: string | readonly string[]): readonly string[] =>\n Array.isArray(to) ? to : [to as string];\n\n/** 우리가 접수하지 못한 건임을 알리는 코드. 솔라피 코드 체계와 겹치지 않는다 */\nexport const CLAWOPS_FAILURE_STATUS_CODE = 'CLAWOPS';\n\nexport interface FallbackEvent {\n to: string;\n source: FallbackSource;\n text: string;\n /** 스윕에서 온 경우에만 채워진다 */\n messageId?: string;\n statusCode?: string;\n}\n\nexport type FallbackBlockedEvent = {\n to: string;\n messageId?: string;\n statusCode?: string;\n} & (\n | Extract<FallbackText, { ok: false }>\n /** 스윕에서만 — 실패했지만 대체발송 대상 코드가 아니다 */\n | { ok: false; reason: 'code_not_eligible' }\n /** 스윕에서만 — 문구는 만들었는데 ClawOps 가 거절했다 */\n | { ok: false; reason: 'send_rejected' }\n);\n\n/**\n * 발송 실패 추적 방식.\n *\n * `polling`·`webhook` 은 일부러 넣지 않았다 — 유니온에 멤버를 추가하는 것은 하위호환이라\n * 필요해지면 그때 붙이면 되고, 미리 선언하면 지키지 못할 약속이 된다.\n */\nexport type FallbackTracking =\n /**\n * 접수 실패(1XXX)만 대체발송. `send()` 응답으로 즉시 판정된다.\n * 추적 옵션은 받지 않는다 — `mode` 없이 `lookbackMs` 를 써 놓고 스윕이 돈다고 오해하는 것을 막는다\n */\n | {\n mode?: undefined;\n intervalMs?: never;\n lookbackMs?: never;\n on?: never;\n except?: never;\n initialCursor?: never;\n onCursor?: never;\n onError?: never;\n }\n /**\n * 리포트를 주기적으로 훑어 발송 실패(3XXX)까지 대체발송한다.\n * 수신자가 카카오톡을 쓰지 않거나 알림톡을 차단한 경우가 여기 해당한다.\n */\n | {\n mode: 'sweep';\n /** 스윕 주기. 기본 5분 */\n intervalMs?: number;\n /** 커서가 없을 때 거슬러 볼 구간. 기본 1시간 */\n lookbackMs?: number;\n /** 대체발송할 상태코드. 주지 않으면 **모든 3XXX** 가 대상이다 */\n on?: readonly string[];\n /**\n * 대상에서 뺄 상태코드. 수신거부·스팸 차단·설정 오류처럼 문자로 덮으면 안 되는 것은\n * `RECOMMENDED_EXCLUDED_CODES` 를 그대로 넘겨 뺄 수 있다\n */\n except?: readonly string[];\n /** 훑을 메시지 타입. 기본은 알림톡만 — 친구톡까지 보려면 넓힌다 */\n types?: SweepOptions['types'];\n /** 프로세스 밖에 커서를 저장했다가 넘길 때 */\n initialCursor?: SweepCursor;\n onCursor?: (cursor: SweepCursor) => void;\n /** 스윕 자체가 실패했을 때. 지정하지 않으면 조용히 다음 주기에 재시도한다 */\n onError?: (error: unknown) => void;\n };\n\nexport type FallbackConfig =\n | boolean\n /**\n * 끈 상태. 다른 옵션은 받지 않는다 —\n * `{ enabled: false, mode: 'sweep' }` 처럼 써 놓고 스윕이 돈다고 오해하는 것을 막는다\n */\n | {\n enabled: false;\n mode?: never;\n field?: never;\n onFallback?: never;\n onBlocked?: never;\n }\n | ({\n enabled: true;\n /** 대체 문구를 읽어올 customFields 키 */\n field?: string;\n onFallback?: (event: FallbackEvent) => void;\n onBlocked?: (event: FallbackBlockedEvent) => void;\n } & FallbackTracking);\n\n/** 켜진 설정. 사용자가 준 모양 그대로라 필드를 옮겨 담을 필요가 없다 */\ntype EnabledFallback = Extract<FallbackConfig, { enabled: true }>;\n\n/** 꺼져 있으면 undefined. 켜져 있으면 원래 유니온 arm 을 그대로 돌려준다 */\nfunction normalizeFallback(config: FallbackConfig | undefined): EnabledFallback | undefined {\n if (config === undefined || config === true) return { enabled: true };\n if (config === false || !config.enabled) return undefined;\n return config;\n}\n\nexport interface ClawOpsMessageServiceOptions {\n /** ClawOps 클라이언트 */\n clawops: ClawOps;\n /** 문자 기본 발신번호. ClawOps 에 등록된 번호여야 한다 */\n from: string;\n /** 알림톡·RCS 등을 계속 쓸 때 넘긴다. 없으면 문자 전용 모드 */\n solapi?: SolapiMessageService;\n /**\n * 알림톡 실패 시 문자로 대체발송.\n * 기본은 접수 실패(1XXX)만 — 발송 실패(3XXX)까지 잡으려면 `mode: 'sweep'` 을 켠다.\n */\n fallback?: FallbackConfig;\n /** 동시에 띄우는 ClawOps 요청 수. 기본 10 */\n concurrency?: number;\n}\n\n/** solapi 는 send 만 노출하고 나머지는 감춘 문자 전용 서비스 */\nexport type ClawOpsTextOnlyService = Pick<SolapiMessageService, 'send'>;\n\n/**\n * 전건이 접수 실패하면 solapi SDK 는 결과 대신 MessageNotReceivedError 를 던진다.\n * 예외지만 정상 경로이고, 안에 실패 목록이 들어 있다.\n */\nfunction isMessageNotReceived(error: unknown): error is { failedMessageList: Failed[] } {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'failedMessageList' in error &&\n Array.isArray((error as { failedMessageList: unknown }).failedMessageList)\n );\n}\n\n/**\n * ClawOps 로 가는 메시지가 있는데 우리가 지킬 수 없는 발송 설정이 오면 막는다.\n * 조용히 버리면 예약 발송이 즉시 발송으로 바뀐다.\n */\nfunction assertConfigSupported(config: SendRequestConfigSchema | undefined): void {\n if (!config) return;\n const unsupported: string[] = [];\n if (config.scheduledDate !== undefined) unsupported.push('scheduledDate');\n if (config.allowDuplicates === false) unsupported.push('allowDuplicates: false');\n if (unsupported.length > 0) {\n throw new SolapiBridgeError(\n `ClawOps 로 보내는 메시지에는 ${unsupported.join(', ')} 를 적용할 수 없습니다. ` +\n '해당 설정이 필요한 발송은 솔라피로 보내거나 호출을 분리하십시오.',\n );\n }\n}\n\nfunction makeGroupInfo(\n base: GroupInfo | null,\n counts: { ok: number; failed: number; replacement: number },\n): GroupInfo {\n if (base) {\n // 솔라피 집계에 **우리 몫을 더한다**. 덮어쓰면 안 된다 —\n // `messageList` 는 `showMessageList: true` 일 때만 오므로 기본 호출에서는 비어 있고,\n // 우리가 센 값으로 덮으면 알림톡 물량이 통째로 0 이 된다.\n // 대체발송분은 솔라피가 이미 실패로 세어 놨으므로 total 에 더하지 않고\n // sentReplacement 로만 올린다 — 솔라피와 같은 의미다\n return {\n ...base,\n count: {\n ...base.count,\n total: base.count.total + counts.ok + counts.failed,\n registeredSuccess: base.count.registeredSuccess + counts.ok,\n registeredFailed: base.count.registeredFailed + counts.failed,\n sentReplacement: base.count.sentReplacement + counts.replacement,\n },\n };\n }\n\n const registered = {\n total: counts.ok + counts.failed,\n registeredSuccess: counts.ok,\n registeredFailed: counts.failed,\n };\n\n const now = new Date().toISOString();\n return {\n count: {\n ...registered,\n sentTotal: 0,\n sentFailed: 0,\n sentSuccess: 0,\n sentPending: 0,\n sentReplacement: counts.replacement,\n refund: 0,\n },\n countForCharge: NO_CHARGE,\n balance: ZERO_CASH,\n point: ZERO_CASH,\n app: { profit: NO_PROFIT, appId: null },\n log: [],\n status: 'PENDING',\n allowDuplicates: false,\n isRefunded: false,\n accountId: '',\n masterAccountId: null,\n apiVersion: '4',\n // 솔라피 그룹이 아니므로 getGroupMessages 로 조회되지 않는다. 접두사로 구분한다\n groupId: `CLAWOPS-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`,\n price: {},\n dateCreated: now,\n dateUpdated: now,\n scheduledDate: null,\n dateSent: null,\n dateCompleted: null,\n };\n}\n\n/** 어디로 보낼지 가른다. 우리가 못 보내는 첨부는 조용히 버리지 않고 여기서 막는다 */\nfunction partition(list: readonly SolapiMessage[]): {\n forSolapi: SolapiMessage[];\n forClawOps: Array<{ to: string; message: SolapiMessage }>;\n} {\n const forSolapi: SolapiMessage[] = [];\n const forClawOps: Array<{ to: string; message: SolapiMessage }> = [];\n\n for (const message of list) {\n const type = upperType(message);\n if ((type !== '' && !OURS.has(type)) || hasVendorOptions(message)) {\n forSolapi.push(message);\n continue;\n }\n if (message.imageId !== undefined) {\n throw new SolapiBridgeError(\n 'imageId 는 솔라피에 업로드된 파일 ID 라 ClawOps 로 전달할 수 없습니다. ' +\n '이미지 첨부가 필요하면 ClawOps 의 mediaUrl 로 직접 발송하십시오.',\n );\n }\n for (const to of recipientsOf(message.to)) forClawOps.push({ to, message });\n }\n return { forSolapi, forClawOps };\n}\n\ninterface Plan {\n outgoing: SolapiMessage;\n source: SolapiMessage;\n}\n\n/**\n * 대체발송에 쓸 발신번호. 없으면 대체발송하지 않는다는 뜻이다.\n *\n * 솔라피 규칙 그대로 — `from` 이 있고 `disableSms` 가 true 가 아니면 \"실패 시 문자로\".\n * 이 판정이 두 곳에 있으면 갈린다. 실제로 예약발송 검증과 계획 수립이 다르게 답한 적이 있다.\n */\nfunction fallbackFrom(message: SolapiMessage | undefined): string | undefined {\n const kakaoOptions = message?.kakaoOptions;\n if (!kakaoOptions || kakaoOptions.disableSms === true) return undefined;\n return message?.from;\n}\n\n/** ClawOps 가 거절한 건을 솔라피 실패 레코드 모양으로 옮긴다 */\nfunction clawopsFailure(to: string, from: string, error: unknown): Failed {\n return {\n to: normalizePhone(to),\n from: normalizePhone(from),\n type: 'SMS',\n statusMessage: error instanceof Error ? error.message : String(error),\n country: '82',\n messageId: '',\n statusCode: CLAWOPS_FAILURE_STATUS_CODE,\n accountId: '',\n };\n}\n\n/**\n * 솔라피 의미를 그대로 읽는다.\n * from 있음 + disableSms !== true → \"실패하면 문자로 대체발송해 달라\"\n * 실행 주체만 우리로 바뀌므로 카카오 옵션이 붙은 메시지에 한해\n * ① from 을 뺀다 — 솔라피에 미등록된 번호면 알림톡 자체가 접수 거부된다\n * ② disableSms 를 true 로 한다 — 솔라피가 중복으로 문자를 쏘지 않도록\n * RCS·NSA·FAX·VOICE 는 각자 발신번호와 옵션 체계가 따로 있어 손대지 않는다.\n */\nfunction buildPlans(messages: readonly SolapiMessage[], fallbackEnabled: boolean): Plan[] {\n return messages.map((message) => {\n const kakaoOptions = message.kakaoOptions;\n // 손댈 수 없거나 손대면 안 되는 경우는 요청을 그대로 넘긴다.\n // from 을 빼고 disableSms 를 켜 놓고 우리도 안 보내면, 고객이 원래 쓰던\n // 솔라피 자체 대체발송까지 죽는다 — 있던 기능을 잃는 쪽이 제일 나쁘다\n //\n // · fallback 을 껐다\n // · 브랜드메시지(kakaoOptions.bms) — 같은 필드를 쓰는 다른 제품이라 대체발송 규칙이 다르다\n // · customFields 가 10개(상태코드 1035)를 다 써서 마커를 심을 자리가 없다\n if (!kakaoOptions || !fallbackEnabled || kakaoOptions.bms) {\n return { outgoing: message, source: message };\n }\n const fields = message.customFields ?? {};\n const canMark = FALLBACK_MARKER_FIELD in fields || Object.keys(fields).length < 10;\n if (!canMark) return { outgoing: message, source: message };\n\n const { from: _from, ...rest } = message;\n\n // 스윕은 계정 전체를 훑으므로, 우리가 보낸 건임을 여기서 표시해 둔다.\n // 이 마커가 없으면 고객이 솔라피로 직접 보낸 알림톡까지 문자를 쏘게 된다.\n // 문구는 심지 않는다 — 발송 경로에 템플릿 조회를 넣으면 그만큼 느려진다.\n // 스윕이 조회한 자리에서 복원한다.\n //\n // mode 와 무관하게 심는다. 크론에서 sweepFailedAlimtalk() 를 부르는 구성도\n // 이 마커에 의존하는데, mode 로 게이트하면 그쪽이 조용히 0건만 훑는다\n let customFields = message.customFields;\n if (fallbackFrom(message) !== undefined) {\n customFields = { ...fields, [FALLBACK_MARKER_FIELD]: FALLBACK_MARKER_VALUE };\n }\n\n return {\n outgoing: {\n ...rest,\n ...(customFields ? { customFields } : {}),\n kakaoOptions: { ...kakaoOptions, disableSms: true },\n },\n source: message,\n };\n });\n}\n\n/**\n * 실패 건을 요청 계획에 되짚는다.\n * 같은 수신자에게 여러 건을 보낼 수 있으므로 수신자당 계획을 줄 세워 하나씩 소비하고,\n * solapi 가 하이픈을 지운 번호로 돌려주므로 양쪽 모두 정규화해서 비교한다.\n */\nfunction matchFailures(\n plans: readonly Plan[],\n failures: readonly Failed[],\n): Array<{ failure: Failed; plan?: Plan }> {\n const byRecipient = new Map<string, number[]>();\n plans.forEach((plan, index) => {\n for (const to of recipientsOf(plan.outgoing.to)) {\n const key = normalizePhone(to);\n const bucket = byRecipient.get(key);\n if (bucket) bucket.push(index);\n else byRecipient.set(key, [index]);\n }\n });\n\n return failures.map((failure) => {\n const index = byRecipient.get(normalizePhone(failure.to))?.shift();\n return { failure, plan: index === undefined ? undefined : plans[index] };\n });\n}\n\nfunction makeSend(\n options: ClawOpsMessageServiceOptions,\n fallbackConfig: EnabledFallback | undefined,\n templateCache: Map<string, Promise<string | undefined>>,\n): SolapiMessageService['send'] {\n const fallbackEnabled = fallbackConfig !== undefined;\n const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;\n\n /** 한 건을 ClawOps 로 보낸다. 실패해도 던지지 않고 결과로 알린다 */\n const viaClawOps = async (\n to: string,\n message: { from?: string; text?: string; subject?: string; type?: unknown },\n statusMessage = '정상 접수',\n ): Promise<{ ok: true; sent: Sent } | { ok: false; failed: Failed }> => {\n const from = normalizePhone(message.from ?? options.from);\n const type = clawopsType(message);\n try {\n const created = await options.clawops.messages.create({\n to: normalizePhone(to),\n from,\n body: message.text ?? '',\n type,\n subject: message.subject,\n });\n return {\n ok: true,\n sent: { messageId: created.messageId, statusCode: '2000', statusMessage },\n };\n } catch (error) {\n // 한 건이 막혀도 이미 보낸 건들의 기록을 잃지 않는다\n return {\n ok: false,\n failed: {\n to: normalizePhone(to),\n from,\n type: type.toUpperCase(),\n statusMessage: error instanceof Error ? error.message : String(error),\n country: '82',\n messageId: '',\n statusCode: CLAWOPS_FAILURE_STATUS_CODE,\n accountId: '',\n },\n };\n }\n };\n\n /** 알림톡 실패분을 문자로 대체발송한다. 문구 조회와 발송이 건마다 왕복이라 동시에 돌린다 */\n const runFallbacks = async (\n plans: readonly Plan[],\n failures: readonly Failed[],\n solapi: SolapiMessageService,\n ): Promise<{ sent: Sent[]; failed: Failed[]; replacement: number; failedCount: number }> => {\n const jobs = matchFailures(plans, failures);\n\n const deliveries = await mapWithConcurrency(jobs, concurrency, async (job) => {\n const from = fallbackFrom(job.plan?.source);\n if (from === undefined) return undefined;\n return deliverFallback({ source: job.plan!.source, to: job.failure.to, from }, solapi, {\n clawops: options.clawops,\n field: fallbackConfig?.field,\n cache: templateCache,\n });\n });\n\n // 콜백은 요청 순서대로 부른다 — 동시 실행 중에 부르면 순서가 뒤섞인다\n const sent: Sent[] = [];\n const failed: Failed[] = [];\n let replacement = 0;\n /** 대체발송을 시도했다가 ClawOps 가 거절한 건. 솔라피 실패는 이미 저쪽이 세므로 뺀다 */\n let failedCount = 0;\n\n deliveries.forEach((delivery, index) => {\n const { failure } = jobs[index]!;\n if (delivery === undefined) {\n failed.push(failure);\n return;\n }\n if (delivery.status === 'blocked') {\n fallbackConfig?.onBlocked?.({ to: failure.to, ...delivery.reason });\n failed.push(failure);\n return;\n }\n if (delivery.status === 'send_failed') {\n // 발송이 성공한 뒤에만 알린다. 먼저 부르면 ClawOps 가 거절한 건까지\n // '문자로 대체발송' 로그가 남아, 운영자가 도달했다고 믿는다\n failed.push(clawopsFailure(failure.to, options.from, delivery.error));\n failedCount += 1;\n return;\n }\n sent.push({\n messageId: delivery.messageId,\n statusCode: '2000',\n statusMessage: '정상 접수(대체발송)',\n });\n replacement += 1;\n fallbackConfig?.onFallback?.({\n to: failure.to,\n source: delivery.source,\n text: delivery.text,\n });\n });\n return { sent, failed, replacement, failedCount };\n };\n\n return async (messages, config) => {\n // 읽기만 하므로 복사하지 않는다\n const list: readonly SolapiMessage[] = Array.isArray(messages) ? messages : [messages];\n const { forSolapi, forClawOps } = partition(list);\n const sent: Sent[] = [];\n const failed: Failed[] = [];\n let replacement = 0;\n // 솔라피 집계에 더할 우리 몫. sent/failed 에는 솔라피 것도 섞여 길이로는 셀 수 없다\n let ourOk = 0;\n let ourFailed = 0;\n\n // 예약 발송처럼 우리가 못 지키는 설정은, 문자가 없어도 대체발송 대상이 있으면 막아야 한다.\n // 알림톡만 있는 배치라도 접수 실패가 나면 대체 문자가 '지금' 나가버린다\n const willFallback =\n fallbackEnabled && forSolapi.some((message) => fallbackFrom(message) !== undefined);\n if (forClawOps.length > 0 || willFallback) assertConfigSupported(config);\n\n if (forClawOps.length > 0) {\n for (const outcome of await mapWithConcurrency(forClawOps, concurrency, (item) =>\n viaClawOps(item.to, item.message),\n )) {\n if (outcome.ok) {\n sent.push(outcome.sent);\n ourOk += 1;\n } else {\n failed.push(outcome.failed);\n ourFailed += 1;\n }\n }\n }\n\n let base: GroupInfo | null = null;\n\n if (forSolapi.length > 0) {\n const solapi = options.solapi;\n if (!solapi) {\n throw new SolapiBridgeError(\n '알림톡·RCS 를 보내려면 solapi 인스턴스를 넘겨야 합니다. ' +\n 'new ClawOpsMessageService({ solapi: new SolapiMessageService(key, secret), … })',\n );\n }\n\n const plans = buildPlans(forSolapi, fallbackEnabled);\n let solapiFailed: readonly Failed[] = [];\n try {\n const response = await solapi.send(\n plans.map((plan) => plan.outgoing) as RequestSendMessagesSchema,\n config,\n );\n base = response.groupInfo;\n sent.push(...(response.messageList ?? []));\n solapiFailed = response.failedMessageList;\n } catch (error) {\n if (!isMessageNotReceived(error)) throw error;\n solapiFailed = error.failedMessageList; // 전건 실패 — groupInfo 는 오지 않는다\n }\n\n if (solapiFailed.length > 0) {\n if (!fallbackEnabled) {\n failed.push(...solapiFailed);\n } else {\n const result = await runFallbacks(plans, solapiFailed, solapi);\n sent.push(...result.sent);\n failed.push(...result.failed);\n replacement += result.replacement;\n ourFailed += result.failedCount;\n }\n }\n }\n\n return {\n groupInfo: makeGroupInfo(base, { ok: ourOk, failed: ourFailed, replacement }),\n messageList: sent,\n failedMessageList: failed,\n };\n };\n}\n\n/**\n * 문자 전용 모드에서 Proxy 가 모든 키를 메서드로 취급하면 JS 의 기본 규약이 깨진다.\n * `then` 이 함수로 보이면 thenable 로 간주돼 async 함수에서 반환할 수조차 없다.\n */\nconst NON_METHOD_KEYS = new Set(['then', 'toJSON', 'inspect']);\n\nconst DEFAULT_SWEEP_INTERVAL_MS = 5 * 60 * 1000;\n/** 처리 집합 상한. 커서가 이미 대부분을 막으므로 이건 재갱신 대비 여유분이다 */\nconst PROCESSED_LIMIT = 10_000;\n\n/**\n * 발송 실패(3XXX)를 주기적으로 되짚는 루프.\n *\n * 타이머는 `unref()` 해서 프로세스를 붙잡지 않는다 — 이 루프가 돈다는 이유로 서버가\n * 종료되지 않는 일은 없어야 한다.\n */\nfunction startSweepLoop(\n options: ClawOpsMessageServiceOptions,\n sweep: EnabledFallback & { mode: 'sweep' },\n solapi: SolapiMessageService,\n templateCache: Map<string, Promise<string | undefined>>,\n): void {\n const processed = new Set<string>();\n let cursor = sweep.initialCursor;\n let running = false;\n\n const tick = async (): Promise<void> => {\n if (running) return; // 한 주기가 길어져도 겹쳐 돌지 않는다\n running = true;\n try {\n const result = await sweepFailedAlimtalk({\n clawops: options.clawops,\n solapi,\n from: options.from,\n cursor,\n lookbackMs: sweep.lookbackMs,\n on: sweep.on,\n except: sweep.except,\n types: sweep.types,\n fallbackField: sweep.field,\n concurrency: options.concurrency,\n templateCache,\n skip: (messageId) => processed.has(messageId),\n onFallback: sweep.onFallback,\n onBlocked: sweep.onBlocked,\n });\n\n cursor = result.cursor;\n for (const messageId of result.processed) processed.add(messageId);\n if (processed.size > PROCESSED_LIMIT) {\n // Set 은 삽입 순서를 지키므로 오래된 절반을 버린다\n for (const messageId of [...processed].slice(0, processed.size - PROCESSED_LIMIT / 2)) {\n processed.delete(messageId);\n }\n }\n // 빈 스윕까지 알리면 커서를 외부에 저장하는 쪽이 하루 288번 같은 값을 다시 쓴다\n if (result.scanned > 0) sweep.onCursor?.(result.cursor);\n } catch (error) {\n // 스윕 실패가 프로세스를 죽이지 않는다. 커서를 옮기지 않았으므로 다음 주기가 같은 구간을 다시 본다\n sweep.onError?.(error);\n } finally {\n running = false;\n }\n };\n\n const timer = setInterval(() => void tick(), sweep.intervalMs ?? DEFAULT_SWEEP_INTERVAL_MS);\n timer.unref?.();\n}\n\nfunction create(options: ClawOpsMessageServiceOptions): SolapiMessageService {\n const fallbackConfig = normalizeFallback(options.fallback);\n const templateCache = new Map<string, Promise<string | undefined>>();\n const send = makeSend(options, fallbackConfig, templateCache);\n\n if (fallbackConfig?.mode === 'sweep') {\n if (!options.solapi) {\n throw new SolapiBridgeError(\n \"mode: 'sweep' 은 솔라피 리포트를 훑는 기능이라 solapi 인스턴스가 필요합니다. \" +\n '문자만 보낸다면 이 설정은 할 일이 없습니다.',\n );\n }\n startSweepLoop(options, fallbackConfig, options.solapi, templateCache);\n }\n\n // 주입된 solapi 인스턴스를 감싼다. 원본은 수정하지 않는다.\n // send 만 우리가 가로채고 나머지 메서드는 그대로 흘려보낸다.\n //\n // 핸들러는 생성 시점에 한 번 고른다 — solapi 유무는 객체 수명 동안 바뀌지 않으므로\n // 프로퍼티 접근마다 분기할 이유가 없고, 문자 전용 모드의 특수 처리도 한쪽에만 모인다\n const solapi = options.solapi;\n const handler: ProxyHandler<SolapiMessageService> = solapi\n ? {\n get(instance, property) {\n if (property === 'send') return send;\n const value = Reflect.get(instance, property, instance);\n return typeof value === 'function' ? value.bind(instance) : value;\n },\n }\n : {\n get(_instance, property) {\n if (property === 'send') return send;\n if (typeof property === 'symbol' || NON_METHOD_KEYS.has(property)) return undefined;\n // Object.prototype 의 것들은 JS 가 답하게 둔다. 막으면 hasOwnProperty 같은 것이 깨진다\n if (property in Object.prototype) {\n return Reflect.get(Object.prototype, property) as unknown;\n }\n return () => {\n throw new SolapiBridgeError(\n `${String(property)}() 는 솔라피 기능입니다. solapi 인스턴스를 넘겨주세요.`,\n );\n };\n },\n };\n\n return new Proxy((solapi ?? {}) as SolapiMessageService, handler);\n}\n\ninterface ClawOpsMessageServiceConstructor {\n /** solapi 를 넘기면 SolapiMessageService 와 완전히 동일한 타입 */\n new (\n options: ClawOpsMessageServiceOptions & { solapi: SolapiMessageService },\n ): SolapiMessageService;\n /** 안 넘기면 문자 전용. 솔라피 기능은 타입에 나타나지 않는다 */\n new (options: ClawOpsMessageServiceOptions & { solapi?: undefined }): ClawOpsTextOnlyService;\n /** 있는지 런타임에만 아는 경우. 두 경우 모두에서 안전한 문자 전용 타입을 준다 */\n new (options: ClawOpsMessageServiceOptions): ClawOpsTextOnlyService;\n}\n\n/**\n * 솔라피 코드를 그대로 두고 문자만 ClawOps 로 보낸다.\n *\n * `new` 의 결과 타입은 `SolapiMessageService` 그 자체라 기존 코드의 타입 자리에 그대로 들어간다.\n */\nexport const ClawOpsMessageService = function (\n this: unknown,\n options: ClawOpsMessageServiceOptions,\n) {\n return create(options);\n} as unknown as ClawOpsMessageServiceConstructor;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamlearners/clawops",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Official Node.js/TypeScript SDK for the ClawOps Voice API",
5
5
  "type": "module",
6
6
  "exports": {