@rikaidev/yomi 0.2.6

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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +81 -0
  3. package/PRIVACY.md +39 -0
  4. package/README.md +659 -0
  5. package/dist/auth/credential-store.js +395 -0
  6. package/dist/auth/keychain.js +256 -0
  7. package/dist/cli/login.js +142 -0
  8. package/dist/line/auth/protocol/e2ee-bootstrap.js +87 -0
  9. package/dist/line/auth/protocol/index.js +3 -0
  10. package/dist/line/auth/protocol/login-metadata.js +159 -0
  11. package/dist/line/auth/protocol/pwless-login-flow.js +83 -0
  12. package/dist/line/auth/pwless/index.js +163 -0
  13. package/dist/line/auth/pwless/steps.js +213 -0
  14. package/dist/line/client/auth-service/client.js +240 -0
  15. package/dist/line/client/e2ee-service/client.js +69 -0
  16. package/dist/line/client/e2ee-service/requests.js +92 -0
  17. package/dist/line/client/e2ee-service/shared-key-log.js +24 -0
  18. package/dist/line/client/e2ee-service/shared-key-payload.js +35 -0
  19. package/dist/line/client/index.js +213 -0
  20. package/dist/line/client/obs-media-client.js +140 -0
  21. package/dist/line/client/parsers.js +69 -0
  22. package/dist/line/client/shop-service/client.js +63 -0
  23. package/dist/line/client/sticker-meta.js +46 -0
  24. package/dist/line/client/sync-service/client.js +317 -0
  25. package/dist/line/client/talk-service/chat-query.js +122 -0
  26. package/dist/line/client/talk-service/command-client.js +242 -0
  27. package/dist/line/client/talk-service/contact-query.js +77 -0
  28. package/dist/line/client/talk-service/message-box-query.js +137 -0
  29. package/dist/line/client/talk-service/message-id-query.js +241 -0
  30. package/dist/line/client/talk-service/query-client.js +219 -0
  31. package/dist/line/client/talk-service/requests.js +383 -0
  32. package/dist/line/client/transport.js +138 -0
  33. package/dist/line/core/auth-session-logout.js +55 -0
  34. package/dist/line/core/auth-session-runtime.js +231 -0
  35. package/dist/line/core/auth-session-service.js +85 -0
  36. package/dist/line/core/chat-runtime-service.js +343 -0
  37. package/dist/line/core/client-runtime.js +17 -0
  38. package/dist/line/core/config.js +67 -0
  39. package/dist/line/core/constants.js +56 -0
  40. package/dist/line/core/contact-query-service.js +20 -0
  41. package/dist/line/core/directory-runtime-service.js +83 -0
  42. package/dist/line/core/e2ee/bootstrap/keychain.js +41 -0
  43. package/dist/line/core/e2ee/crypto/crypto-primitives.js +98 -0
  44. package/dist/line/core/e2ee/index.js +6 -0
  45. package/dist/line/core/e2ee/keys/decrypt.js +110 -0
  46. package/dist/line/core/e2ee/keys/encrypt.js +117 -0
  47. package/dist/line/core/e2ee/keys/group-key.js +260 -0
  48. package/dist/line/core/e2ee/keys/key-manager.js +231 -0
  49. package/dist/line/core/e2ee/keys/key-payload.js +130 -0
  50. package/dist/line/core/e2ee/keys/key-types.js +1 -0
  51. package/dist/line/core/e2ee/keys/message-crypto.js +146 -0
  52. package/dist/line/core/e2ee/keys/message-decrypt.js +390 -0
  53. package/dist/line/core/e2ee/keys/peer-key.js +191 -0
  54. package/dist/line/core/e2ee/recovery/bootstrap-keys.js +161 -0
  55. package/dist/line/core/e2ee/types.js +4 -0
  56. package/dist/line/core/group-query-service.js +20 -0
  57. package/dist/line/core/media-decrypt.js +78 -0
  58. package/dist/line/core/media-encrypt.js +97 -0
  59. package/dist/line/core/mention.js +89 -0
  60. package/dist/line/core/message-command-service.js +454 -0
  61. package/dist/line/core/message-query-service.js +161 -0
  62. package/dist/line/core/service.js +134 -0
  63. package/dist/line/core/session-state/index.js +243 -0
  64. package/dist/line/core/session-state/persist.js +192 -0
  65. package/dist/line/core/store.js +8 -0
  66. package/dist/line/core/thrift/binary/binary-reader.js +151 -0
  67. package/dist/line/core/thrift/binary/binary-writer.js +157 -0
  68. package/dist/line/core/thrift/binary/type-codec-dsl.js +42 -0
  69. package/dist/line/core/thrift/compact/compact-reader.js +180 -0
  70. package/dist/line/core/thrift/compact/compact-writer.js +161 -0
  71. package/dist/line/core/thrift/compact/type-codec-dsl.js +66 -0
  72. package/dist/line/core/thrift/fields/builders.js +124 -0
  73. package/dist/line/core/thrift/index.js +12 -0
  74. package/dist/line/core/thrift/message/decoder.js +55 -0
  75. package/dist/line/core/thrift/message/encoder.js +25 -0
  76. package/dist/line/core/thrift/primitives/varint.js +64 -0
  77. package/dist/line/core/thrift/struct/reader.js +172 -0
  78. package/dist/line/core/thrift/struct/writer.js +189 -0
  79. package/dist/line/core/thrift/types.js +23 -0
  80. package/dist/line/core/video-thumbnail.js +104 -0
  81. package/dist/mcp/handlers/contacts.js +166 -0
  82. package/dist/mcp/handlers/conversations.js +193 -0
  83. package/dist/mcp/handlers/groups.js +123 -0
  84. package/dist/mcp/handlers/index.js +15 -0
  85. package/dist/mcp/handlers/login.js +473 -0
  86. package/dist/mcp/handlers/media.js +69 -0
  87. package/dist/mcp/handlers/message-actions.js +62 -0
  88. package/dist/mcp/handlers/scope.js +120 -0
  89. package/dist/mcp/handlers/search.js +196 -0
  90. package/dist/mcp/handlers/send.js +523 -0
  91. package/dist/mcp/handlers/shared.js +84 -0
  92. package/dist/mcp/handlers/stickers.js +87 -0
  93. package/dist/mcp/media.js +228 -0
  94. package/dist/mcp/names.js +133 -0
  95. package/dist/mcp/phi-guard.js +105 -0
  96. package/dist/mcp/policy.js +34 -0
  97. package/dist/mcp/server.js +301 -0
  98. package/dist/mcp/tools.js +761 -0
  99. package/dist/mcp/ui/capability.js +26 -0
  100. package/dist/mcp/ui/login-app-protocol.js +118 -0
  101. package/dist/mcp/ui/login-app-styles.js +142 -0
  102. package/dist/mcp/ui/login-app-view.js +361 -0
  103. package/dist/mcp/ui/login-app.js +72 -0
  104. package/dist/mcp/ui/login-structured-content.js +1 -0
  105. package/dist/mcp/ui/resource.js +35 -0
  106. package/dist/mcp/ui/tools-with-ui.js +40 -0
  107. package/dist/search/bigram.js +86 -0
  108. package/dist/search/capture.js +140 -0
  109. package/dist/search/collector.js +176 -0
  110. package/dist/search/default-embedder.js +101 -0
  111. package/dist/search/embedder.js +13 -0
  112. package/dist/search/scope.js +142 -0
  113. package/dist/search/sqlite.js +141 -0
  114. package/dist/search/store.js +469 -0
  115. package/dist/util/data-dir.js +55 -0
  116. package/dist/util/log.js +86 -0
  117. package/dist/version.js +7 -0
  118. package/package.json +63 -0
  119. package/run.mjs +71 -0
@@ -0,0 +1,383 @@
1
+ import { CONTENT_TYPE } from '../../core/constants.js';
2
+ import { boolField, byteField, i32Field, i64Field, listField, mapField, setField, stringField, structField, } from '../../core/thrift/index.js';
3
+ /**
4
+ * Normalize sendMessage inputs into one consistent options object.
5
+ *
6
+ * @param to - Recipient chat id or options object.
7
+ * @param text - Legacy text argument.
8
+ * @returns Normalized message options.
9
+ */
10
+ export function normalizeSendMessageOptions(to, text) {
11
+ if (typeof to === 'object' && to !== null) {
12
+ return {
13
+ to: to.to,
14
+ text: to.text,
15
+ contentType: to.contentType ?? CONTENT_TYPE.NONE,
16
+ contentMetadata: to.contentMetadata ?? {},
17
+ chunks: to.chunks ?? null,
18
+ relatedMessageId: to.relatedMessageId ?? null,
19
+ messageRelationType: to.messageRelationType ?? null,
20
+ relatedMessageServiceCode: to.relatedMessageServiceCode ?? null,
21
+ location: to.location ?? null,
22
+ };
23
+ }
24
+ return {
25
+ to,
26
+ text,
27
+ contentType: CONTENT_TYPE.NONE,
28
+ contentMetadata: {},
29
+ chunks: null,
30
+ relatedMessageId: null,
31
+ messageRelationType: null,
32
+ relatedMessageServiceCode: null,
33
+ location: null,
34
+ };
35
+ }
36
+ /**
37
+ * Build the sendMessage nested payload fields.
38
+ *
39
+ * @param options - Normalized send-message options.
40
+ * @returns Thrift struct fields.
41
+ */
42
+ export function buildSendMessagePayload(options) {
43
+ const fields = [stringField(2, options.to), i32Field(15, options.contentType)];
44
+ if (options.text != null) {
45
+ fields.push(stringField(10, options.text));
46
+ }
47
+ if (options.contentMetadata &&
48
+ Object.keys(options.contentMetadata).length > 0) {
49
+ fields.push(mapField(18, 11, 11, options.contentMetadata));
50
+ }
51
+ if (Array.isArray(options.chunks)) {
52
+ fields.push(listField(20, 11, options.chunks));
53
+ }
54
+ if (options.relatedMessageId) {
55
+ fields.push(stringField(21, options.relatedMessageId));
56
+ // A reply needs messageRelationType (field 22, MessageRelationType enum,
57
+ // REPLY=3) AND relatedMessageServiceCode (field 24, ServiceCode enum,
58
+ // TALK=1) alongside the related id — both are i32 enums, not strings, and
59
+ // relatedMessageId alone is not rendered as a quoted reply.
60
+ if (options.messageRelationType != null) {
61
+ fields.push(i32Field(22, options.messageRelationType));
62
+ }
63
+ if (options.relatedMessageServiceCode != null) {
64
+ fields.push(i32Field(24, options.relatedMessageServiceCode));
65
+ }
66
+ }
67
+ if (options.location) {
68
+ fields.push(structField(11, options.location));
69
+ }
70
+ return fields;
71
+ }
72
+ /**
73
+ * Build the sendMessage request fields.
74
+ *
75
+ * @param options - Normalized send-message options.
76
+ * @returns Thrift request fields.
77
+ */
78
+ export function buildSendMessageRequest(options) {
79
+ return [i32Field(1, 0), structField(2, buildSendMessagePayload(options))];
80
+ }
81
+ /**
82
+ * Build the getAllChatMids request fields.
83
+ *
84
+ * @param syncReason - Talk sync reason constant.
85
+ * @returns Thrift request fields.
86
+ */
87
+ export function buildGetAllChatMidsRequest(syncReason) {
88
+ return [
89
+ structField(1, [boolField(1, true), boolField(2, true)]),
90
+ i32Field(2, syncReason),
91
+ ];
92
+ }
93
+ /**
94
+ * Build the getChats request fields.
95
+ *
96
+ * @param chatMids - Requested chat MIDs.
97
+ * @param withMembers - Whether to include members.
98
+ * @param syncReason - Talk sync reason constant.
99
+ * @returns Thrift request fields.
100
+ */
101
+ export function buildGetChatsRequest(chatMids, withMembers, syncReason) {
102
+ return [
103
+ structField(1, [
104
+ listField(1, 11, chatMids),
105
+ boolField(2, withMembers),
106
+ boolField(3, true),
107
+ ]),
108
+ i32Field(2, syncReason),
109
+ ];
110
+ }
111
+ /**
112
+ * Build the getMessageBoxes request fields.
113
+ *
114
+ * @param request - Message-box request payload.
115
+ * @param syncReason - Talk sync reason constant.
116
+ * @returns Thrift request fields.
117
+ */
118
+ export function buildGetMessageBoxesRequest(request, syncReason) {
119
+ return [structField(2, request), i32Field(3, syncReason)];
120
+ }
121
+ /**
122
+ * Build the getPreviousMessagesV2WithRequest request fields.
123
+ *
124
+ * @param request - Previous-message request payload.
125
+ * @param endMessageIdFields - Cursor struct fields.
126
+ * @param syncReason - Talk sync reason constant.
127
+ * @returns Thrift request fields.
128
+ */
129
+ export function buildPreviousMessagesRequest(request, endMessageIdFields, syncReason) {
130
+ return [
131
+ structField(2, [
132
+ stringField(1, request.messageBoxId),
133
+ structField(2, endMessageIdFields),
134
+ i32Field(3, request.messagesCount),
135
+ boolField(4, Boolean(request.withReadCount)),
136
+ boolField(5, Boolean(request.receivedOnly)),
137
+ ]),
138
+ i32Field(3, syncReason),
139
+ ];
140
+ }
141
+ /**
142
+ * Build the getRecentMessagesV2 request fields.
143
+ *
144
+ * @param chatId - Target chat MID.
145
+ * @param count - Maximum number of messages.
146
+ * @returns Thrift request fields.
147
+ */
148
+ export function buildRecentMessagesRequest(chatId, count) {
149
+ return [stringField(2, chatId), i32Field(3, count)];
150
+ }
151
+ /**
152
+ * Build a message-content download request.
153
+ *
154
+ * @param requestId - Client request identifier.
155
+ * @param messageId - LINE message identifier.
156
+ * @returns Thrift request fields.
157
+ */
158
+ export function buildDownloadMessageContentRequest(requestId, messageId) {
159
+ return [stringField(1, requestId), stringField(2, messageId)];
160
+ }
161
+ /**
162
+ * Build a single-MID lookup request.
163
+ *
164
+ * @param mid - Target MID.
165
+ * @returns Thrift request fields.
166
+ */
167
+ export function buildMidLookupRequest(mid) {
168
+ return [stringField(2, mid)];
169
+ }
170
+ /**
171
+ * Build a multi-MID lookup request.
172
+ *
173
+ * @param mids - Target MIDs.
174
+ * @returns Thrift request fields.
175
+ */
176
+ export function buildMidListRequest(mids) {
177
+ return [listField(2, 11, mids)];
178
+ }
179
+ /**
180
+ * Build the getAllContactIds request fields.
181
+ *
182
+ * @returns Thrift request fields.
183
+ */
184
+ export function buildGetAllContactIdsRequest() {
185
+ return [i32Field(1, 0)];
186
+ }
187
+ /**
188
+ * Build the sendChatChecked request fields.
189
+ *
190
+ * @param chatMid - Chat MID to mark read.
191
+ * @param lastMessageId - Message id to mark read up to.
192
+ * @param sessionId - Client session id (default 0).
193
+ * @returns Thrift request fields.
194
+ */
195
+ export function buildSendChatCheckedRequest(chatMid, lastMessageId, sessionId = 0) {
196
+ return [
197
+ i32Field(1, 0),
198
+ stringField(2, chatMid),
199
+ stringField(3, lastMessageId),
200
+ byteField(4, sessionId),
201
+ ];
202
+ }
203
+ /**
204
+ * Build the updateChat request fields to rename a group/chat.
205
+ *
206
+ * LINE's modern unified chat API updates a Chat struct and names which
207
+ * attribute changed via `updatedAttribute` (NAME=1). The whole request is a
208
+ * single struct arg at field 1: `{ seq, chat{ type, chatMid, name }, attr }`.
209
+ *
210
+ * @param chatMid - Chat/group MID to rename.
211
+ * @param name - New chat name.
212
+ * @returns Thrift request fields.
213
+ */
214
+ export function buildUpdateChatNameRequest(chatMid, name) {
215
+ return [
216
+ structField(1, [
217
+ i32Field(1, 0),
218
+ structField(2, [
219
+ i32Field(1, 1),
220
+ stringField(2, chatMid),
221
+ stringField(6, name),
222
+ ]),
223
+ i32Field(3, 1),
224
+ ]),
225
+ ];
226
+ }
227
+ /**
228
+ * Build the inviteIntoChat request fields.
229
+ *
230
+ * Single struct arg at field 1: `{ seq, to, targetMids }`.
231
+ *
232
+ * @param chatMid - Chat/group MID to invite into.
233
+ * @param mids - MIDs to invite.
234
+ * @returns Thrift request fields.
235
+ */
236
+ export function buildInviteIntoChatRequest(chatMid, mids) {
237
+ return [
238
+ structField(1, [
239
+ i32Field(1, 0),
240
+ stringField(2, chatMid),
241
+ setField(3, 11, mids),
242
+ ]),
243
+ ];
244
+ }
245
+ /**
246
+ * Build the deleteOtherFromChat request fields (kick members).
247
+ *
248
+ * Single struct arg at field 1: `{ seq, to, targetMids }`.
249
+ *
250
+ * @param chatMid - Chat/group MID to remove members from.
251
+ * @param mids - MIDs to remove.
252
+ * @returns Thrift request fields.
253
+ */
254
+ export function buildDeleteOtherFromChatRequest(chatMid, mids) {
255
+ return [
256
+ structField(1, [
257
+ i32Field(1, 0),
258
+ stringField(2, chatMid),
259
+ setField(3, 11, mids),
260
+ ]),
261
+ ];
262
+ }
263
+ /**
264
+ * Build the deleteSelfFromChat request fields (leave a chat/group).
265
+ *
266
+ * Single struct arg at field 1: `{ seq, to }`.
267
+ *
268
+ * @param chatMid - Chat/group MID to leave.
269
+ * @returns Thrift request fields.
270
+ */
271
+ export function buildDeleteSelfFromChatRequest(chatMid) {
272
+ return [structField(1, [i32Field(1, 0), stringField(2, chatMid)])];
273
+ }
274
+ /**
275
+ * Build the createChat request fields (create a new group/room).
276
+ *
277
+ * Single struct arg at field 1: CreateChatRequest
278
+ * `{ reqSeq, type, name, targetUserMids }`. `type` is the LINE chat type —
279
+ * 0 = GROUP (invitees must accept before joining), 1 = ROOM (members are added
280
+ * directly). targetUserMids is a set on the wire; a list encodes identically.
281
+ *
282
+ * @param name - Chat name.
283
+ * @param mids - Initial member MIDs.
284
+ * @param chatType - LINE chat type (0 = group, 1 = room).
285
+ * @returns Thrift request fields.
286
+ */
287
+ export function buildCreateChatRequest(name, mids, chatType = 1) {
288
+ return [
289
+ structField(1, [
290
+ i32Field(1, 0),
291
+ i32Field(2, chatType),
292
+ stringField(3, name),
293
+ setField(4, 11, mids),
294
+ ]),
295
+ ];
296
+ }
297
+ /**
298
+ * Build the react request fields (add a predefined reaction to a message).
299
+ *
300
+ * Single struct arg at field 1: `{ reqSeq, messageId, reactionType{ predefined } }`.
301
+ * The messageId is a thrift i64 (LINE message ids are 64-bit), so it is passed
302
+ * as a BigInt. `reactionType` is LINE's predefinedReactionType enum:
303
+ * 2 = LIKE 👍, 3 = LOVE ❤️, 4 = LAUGH 😆, 5 = SURPRISE 😮, 6 = SAD 😢, 7 = ANGRY 😡.
304
+ *
305
+ * @param messageId - Target message id (numeric string).
306
+ * @param reactionType - Predefined reaction type (default 2 = LIKE).
307
+ * @returns Thrift request fields.
308
+ */
309
+ export function buildReactRequest(messageId, reactionType = 2) {
310
+ return [
311
+ structField(1, [
312
+ i32Field(1, 0),
313
+ i64Field(2, BigInt(messageId)),
314
+ structField(3, [i32Field(1, reactionType)]),
315
+ ]),
316
+ ];
317
+ }
318
+ /**
319
+ * Build the cancelReaction request fields (remove this account's reaction).
320
+ *
321
+ * Single struct arg at field 1: `{ reqSeq, messageId }`. The messageId is a
322
+ * thrift i64, passed as a BigInt.
323
+ *
324
+ * @param messageId - Target message id (numeric string).
325
+ * @returns Thrift request fields.
326
+ */
327
+ export function buildCancelReactionRequest(messageId) {
328
+ return [structField(1, [i32Field(1, 0), i64Field(2, BigInt(messageId))])];
329
+ }
330
+ /**
331
+ * Build the unsendMessage request fields (retract one of this account's own
332
+ * messages for everyone).
333
+ *
334
+ * Top-level fields (NOT wrapped in a request struct): `{ seq, messageId }`.
335
+ * Unlike react, unsendMessage takes the messageId as a plain string, not an
336
+ * i64.
337
+ *
338
+ * @param messageId - Message id to unsend (numeric string).
339
+ * @returns Thrift request fields.
340
+ */
341
+ export function buildUnsendMessageRequest(messageId) {
342
+ return [i32Field(1, 0), stringField(2, messageId)];
343
+ }
344
+ /**
345
+ * Build the findAndAddContactsByMid request fields (add a friend by MID).
346
+ *
347
+ * Top-level fields: `{ reqSeq, mid, type, reference }`. `type` is 0; `reference`
348
+ * is a JSON breadcrumb LINE records for where the add came from.
349
+ *
350
+ * @param mid - MID of the person to add.
351
+ * @param reference - JSON reference breadcrumb.
352
+ * @returns Thrift request fields.
353
+ */
354
+ export function buildFindAndAddContactsByMidRequest(mid, reference = '{"screen":"chat","spec":"native"}') {
355
+ return [
356
+ i32Field(1, 0),
357
+ stringField(2, mid),
358
+ i32Field(3, 0),
359
+ stringField(4, reference),
360
+ ];
361
+ }
362
+ /**
363
+ * Build a `{ reqSeq, mid }` contact-action request — shared by blockContact and
364
+ * unblockContact, which have identical wire shapes (only the method differs).
365
+ *
366
+ * @param mid - Target contact MID.
367
+ * @returns Thrift request fields.
368
+ */
369
+ export function buildContactMidActionRequest(mid) {
370
+ return [i32Field(1, 0), stringField(2, mid)];
371
+ }
372
+ /**
373
+ * Build the acceptChatInvitation request fields (join a group you were invited
374
+ * to).
375
+ *
376
+ * Single struct arg at field 1: `{ reqSeq, to }`.
377
+ *
378
+ * @param chatMid - Group/chat MID to accept the invitation for.
379
+ * @returns Thrift request fields.
380
+ */
381
+ export function buildAcceptChatInvitationRequest(chatMid) {
382
+ return [structField(1, [i32Field(1, 0), stringField(2, chatMid)])];
383
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Shared HTTP transport for LINE TCompact protocol.
3
+ * Used by both LinePwlessLogin and LineClient.
4
+ */
5
+ import { Buffer } from 'node:buffer';
6
+ import https from 'node:https';
7
+ import { createCliLogger } from '../../util/log.js';
8
+ import { LINE_APP_CONFIG } from '../core/config.js';
9
+ import { decodeResponseMessage } from '../core/thrift/index.js';
10
+ const log = createCliLogger('LINE');
11
+ /**
12
+ * Resolve the transport logger for the current LINE runtime.
13
+ *
14
+ * @param options - Optional transport options.
15
+ * @returns Logger used for transport diagnostics.
16
+ */
17
+ function getTransportLog(options = {}) {
18
+ return options.logger || log;
19
+ }
20
+ /**
21
+ * Determine whether one HTTP status is expected during long-poll style LINE auth flows.
22
+ *
23
+ * @param path - Request path.
24
+ * @param statusCode - HTTP status code.
25
+ * @param bodyLength - Response body length.
26
+ * @returns True when the transport event should be downgraded from warning noise.
27
+ */
28
+ function isExpectedHttpStatus(path, statusCode, bodyLength) {
29
+ return (statusCode === 410 &&
30
+ bodyLength === 0 &&
31
+ (path === LINE_APP_CONFIG.qrLongPollPath ||
32
+ path === LINE_APP_CONFIG.pwlessLongPollPath));
33
+ }
34
+ /**
35
+ * Send a TCompact request to LINE server.
36
+ *
37
+ * @param host - The LINE server hostname
38
+ * @param path - The API endpoint path
39
+ * @param data - TCompact-encoded body
40
+ * @param extraHeaders - Additional HTTP headers
41
+ * @param timeout - Request timeout in milliseconds
42
+ * @param options - Optional transport logging options
43
+ * @returns Promise resolving to decoded response with optional nextToken
44
+ */
45
+ export function sendRequest(host, path, data, extraHeaders = {}, timeout = 30000, options = {}) {
46
+ return new Promise((resolve, reject) => {
47
+ const transportLog = getTransportLog(options);
48
+ const headers = {
49
+ 'Content-Type': 'application/x-thrift',
50
+ Accept: 'application/x-thrift',
51
+ 'X-Line-Application': LINE_APP_CONFIG.lineApp,
52
+ 'User-Agent': LINE_APP_CONFIG.userAgent,
53
+ 'x-lal': 'ja_JP',
54
+ 'x-lpv': '1',
55
+ ...extraHeaders,
56
+ 'Content-Length': data.length,
57
+ };
58
+ const req = https.request({
59
+ hostname: host,
60
+ port: 443,
61
+ path,
62
+ method: 'POST',
63
+ headers,
64
+ timeout,
65
+ }, (res) => {
66
+ const chunks = [];
67
+ res.on('data', (chunk) => chunks.push(chunk));
68
+ res.on('end', () => {
69
+ const body = Buffer.concat(chunks);
70
+ const nextToken = extractNextToken(res.headers);
71
+ if (nextToken) {
72
+ transportLog.debug?.('transport.token_rotated', { path });
73
+ }
74
+ if (res.statusCode && res.statusCode >= 400) {
75
+ const eventName = isExpectedHttpStatus(path, res.statusCode, body.length)
76
+ ? 'transport.http_expected'
77
+ : 'transport.http_error';
78
+ const logMethod = isExpectedHttpStatus(path, res.statusCode, body.length)
79
+ ? transportLog.debug
80
+ : transportLog.warn;
81
+ logMethod?.(eventName, {
82
+ path,
83
+ status: res.statusCode,
84
+ bytes: body.length,
85
+ });
86
+ }
87
+ if (body.length > 0) {
88
+ try {
89
+ const decoded = decodeResponseMessage(body);
90
+ resolve({ ...decoded, statusCode: res.statusCode, nextToken });
91
+ }
92
+ catch (error) {
93
+ console.error(`[LINE] Decode failed on ${path}: ${error?.message || error} [${body.length}B]`);
94
+ console.error(`[LINE] Decode first 64B on ${path}: ${body.subarray(0, 64).toString('hex')}`);
95
+ console.error(`[LINE] Decode last 64B on ${path}: ${body.subarray(Math.max(0, body.length - 64)).toString('hex')}`);
96
+ reject(error);
97
+ }
98
+ }
99
+ else {
100
+ resolve({
101
+ error: 'empty_response',
102
+ statusCode: res.statusCode,
103
+ nextToken,
104
+ });
105
+ }
106
+ });
107
+ });
108
+ req.on('timeout', () => {
109
+ req.destroy();
110
+ resolve({ error: 'timeout' });
111
+ });
112
+ req.on('error', (err) => {
113
+ const errorCode = err.code;
114
+ if (errorCode === 'ECONNRESET' ||
115
+ errorCode === 'ETIMEDOUT' ||
116
+ err.message.includes('socket hang up') ||
117
+ err.message.includes('ETIMEDOUT')) {
118
+ resolve({ error: 'timeout' });
119
+ }
120
+ else {
121
+ reject(err);
122
+ }
123
+ });
124
+ req.write(data);
125
+ req.end();
126
+ });
127
+ }
128
+ /**
129
+ * Extract new access token from LINE response headers.
130
+ * LINE rotates tokens by sending x-line-next-access in any response.
131
+ *
132
+ * @param headers - HTTP response headers
133
+ * @returns New token string or undefined
134
+ */
135
+ function extractNextToken(headers) {
136
+ const next = headers['x-line-next-access'];
137
+ return typeof next === 'string' && next.length > 0 ? next : undefined;
138
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * LINE auth/session logout runtime.
3
+ */
4
+ /**
5
+ * Clear stored credentials while preserving the phone number and region, so
6
+ * a later re-login can prefill both (and, on a client without MCP
7
+ * elicitation, run fully argument-free via `login`). Neither is auth
8
+ * material; this matches invalidateSession, which already keeps them.
9
+ *
10
+ * @param service - LineProtocolService instance
11
+ */
12
+ async function clearCredentialsKeepingPhone(service) {
13
+ const savedPhone = await service.credentialStore?.get?.('line_phone');
14
+ const savedRegion = await service.credentialStore?.get?.('line_region');
15
+ await service.credentialStore.clearAll();
16
+ if (savedPhone) {
17
+ await service.credentialStore?.set?.('line_phone', savedPhone);
18
+ }
19
+ if (savedRegion) {
20
+ await service.credentialStore?.set?.('line_region', savedRegion);
21
+ }
22
+ }
23
+ /**
24
+ * Log out from LINE by attempting a server-side logout first, then clearing the
25
+ * local session snapshot regardless of server acknowledgement.
26
+ *
27
+ * @param service - LineProtocolService instance
28
+ * @param disconnectedState - DISCONNECTED state constant from service
29
+ */
30
+ export async function performLogout(service, disconnectedState) {
31
+ try {
32
+ service.client?.stopPolling?.();
33
+ }
34
+ catch {
35
+ // Polling may already be stopped
36
+ }
37
+ try {
38
+ await service.client?.logoutZ?.();
39
+ }
40
+ catch (error) {
41
+ service.logger?.warn?.('line.logout.remote_failed', {
42
+ error: error?.message || String(error),
43
+ });
44
+ }
45
+ await clearCredentialsKeepingPhone(service);
46
+ service.client = null;
47
+ service.profile = null;
48
+ service.e2eeWarning = false;
49
+ service.loginRequired = false;
50
+ service.loginReason = null;
51
+ service.nameCache.clear();
52
+ service.chatCache.clear();
53
+ service.setState(disconnectedState);
54
+ service.emit('line:loginRequired');
55
+ }