agent-messenger 2.27.2 → 2.29.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.
- package/.claude-plugin/README.md +11 -1
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/AGENTS.md +4 -0
- package/README.md +58 -10
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/discord/client.d.ts +6 -2
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +10 -3
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +1 -1
- package/dist/src/platforms/imessage/cli.d.ts +5 -0
- package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
- package/dist/src/platforms/imessage/cli.js +31 -0
- package/dist/src/platforms/imessage/cli.js.map +1 -0
- package/dist/src/platforms/imessage/client.d.ts +43 -0
- package/dist/src/platforms/imessage/client.d.ts.map +1 -0
- package/dist/src/platforms/imessage/client.js +190 -0
- package/dist/src/platforms/imessage/client.js.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.js +100 -0
- package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.js +42 -0
- package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.js +101 -0
- package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
- package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
- package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/index.js +7 -0
- package/dist/src/platforms/imessage/commands/index.js.map +1 -0
- package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
- package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/message.js +127 -0
- package/dist/src/platforms/imessage/commands/message.js.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.js +57 -0
- package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.js +53 -0
- package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.js +20 -0
- package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.js +111 -0
- package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.js +13 -0
- package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
- package/dist/src/platforms/imessage/errors.d.ts +3 -0
- package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
- package/dist/src/platforms/imessage/errors.js +25 -0
- package/dist/src/platforms/imessage/errors.js.map +1 -0
- package/dist/src/platforms/imessage/index.d.ts +7 -0
- package/dist/src/platforms/imessage/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/index.js +5 -0
- package/dist/src/platforms/imessage/index.js.map +1 -0
- package/dist/src/platforms/imessage/rpc.d.ts +20 -0
- package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
- package/dist/src/platforms/imessage/rpc.js +133 -0
- package/dist/src/platforms/imessage/rpc.js.map +1 -0
- package/dist/src/platforms/imessage/types.d.ts +59 -0
- package/dist/src/platforms/imessage/types.d.ts.map +1 -0
- package/dist/src/platforms/imessage/types.js +29 -0
- package/dist/src/platforms/imessage/types.js.map +1 -0
- package/dist/src/platforms/instagram/cli.js +1 -1
- package/dist/src/platforms/instagram/client.d.ts +29 -2
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +360 -53
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +123 -11
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts +4 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.js +17 -1
- package/dist/src/platforms/instagram/credential-manager.js.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
- package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
- package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
- package/dist/src/platforms/instagram/index.d.ts +2 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +2 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
- package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
- package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
- package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.js +151 -0
- package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
- package/dist/src/platforms/instagram/types.d.ts +10 -8
- package/dist/src/platforms/instagram/types.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +1 -1
- package/dist/src/platforms/line/cli.js +1 -1
- package/dist/src/platforms/teams/app-config.d.ts +36 -0
- package/dist/src/platforms/teams/app-config.d.ts.map +1 -0
- package/dist/src/platforms/teams/app-config.js +69 -0
- package/dist/src/platforms/teams/app-config.js.map +1 -0
- package/dist/src/platforms/teams/client.d.ts +19 -2
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +295 -7
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +10 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +137 -3
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/file.d.ts +3 -0
- package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/file.js +59 -2
- package/dist/src/platforms/teams/commands/file.js.map +1 -1
- package/dist/src/platforms/teams/commands/message.d.ts +11 -0
- package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/message.js +121 -2
- package/dist/src/platforms/teams/commands/message.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts +23 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/credential-manager.js +46 -2
- package/dist/src/platforms/teams/credential-manager.js.map +1 -1
- package/dist/src/platforms/teams/device-code.d.ts +48 -0
- package/dist/src/platforms/teams/device-code.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-code.js +199 -0
- package/dist/src/platforms/teams/device-code.js.map +1 -0
- package/dist/src/platforms/teams/device-login.d.ts +37 -0
- package/dist/src/platforms/teams/device-login.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-login.js +129 -0
- package/dist/src/platforms/teams/device-login.js.map +1 -0
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +12 -0
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/index.d.ts +7 -3
- package/dist/src/platforms/teams/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/index.js +5 -2
- package/dist/src/platforms/teams/index.js.map +1 -1
- package/dist/src/platforms/teams/listener.d.ts +35 -0
- package/dist/src/platforms/teams/listener.d.ts.map +1 -0
- package/dist/src/platforms/teams/listener.js +236 -0
- package/dist/src/platforms/teams/listener.js.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts +3 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.js +33 -0
- package/dist/src/platforms/teams/realm-discovery.js.map +1 -0
- package/dist/src/platforms/teams/token-extractor.d.ts +3 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +60 -0
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/token-provider.d.ts +25 -0
- package/dist/src/platforms/teams/token-provider.d.ts.map +1 -0
- package/dist/src/platforms/teams/token-provider.js +190 -0
- package/dist/src/platforms/teams/token-provider.js.map +1 -0
- package/dist/src/platforms/teams/trouter.d.ts +31 -0
- package/dist/src/platforms/teams/trouter.d.ts.map +1 -0
- package/dist/src/platforms/teams/trouter.js +181 -0
- package/dist/src/platforms/teams/trouter.js.map +1 -0
- package/dist/src/platforms/teams/types.d.ts +94 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +30 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/platforms/webex/cli.js +1 -1
- package/dist/src/platforms/webex/types.d.ts +1 -1
- package/dist/src/platforms/webexbot/cli.js +1 -1
- package/dist/src/platforms/wechatbot/cli.js +1 -1
- package/dist/src/platforms/whatsapp/cli.js +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +1 -1
- package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
- package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/imessage-adapter.js +110 -0
- package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
- package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/instagram-adapter.js +16 -0
- package/dist/src/tui/adapters/instagram-adapter.js.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.d.ts +3 -0
- package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.js +21 -0
- package/dist/src/tui/adapters/teams-adapter.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +12 -3
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +1 -1
- package/docs/content/docs/cli/imessage.mdx +99 -0
- package/docs/content/docs/cli/instagram.mdx +2 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/teams.mdx +22 -0
- package/docs/content/docs/index.mdx +9 -6
- package/docs/content/docs/quick-start.mdx +2 -0
- package/docs/content/docs/sdk/instagram.mdx +90 -5
- package/docs/content/docs/tui.mdx +4 -3
- package/package.json +10 -2
- package/scripts/postbuild.ts +12 -15
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +133 -0
- package/skills/agent-imessage/references/authentication.md +66 -0
- package/skills/agent-imessage/references/permissions.md +31 -0
- package/skills/agent-imessage/references/setup.md +49 -0
- package/skills/agent-instagram/SKILL.md +72 -2
- package/skills/agent-instagram/references/authentication.md +11 -0
- package/skills/agent-instagram/references/common-patterns.md +36 -0
- package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +59 -6
- package/skills/agent-teams/references/authentication.md +31 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/discord/client.test.ts +86 -0
- package/src/platforms/discord/client.ts +10 -3
- package/src/platforms/imessage/cli.ts +39 -0
- package/src/platforms/imessage/client.test.ts +137 -0
- package/src/platforms/imessage/client.ts +253 -0
- package/src/platforms/imessage/commands/auth.ts +142 -0
- package/src/platforms/imessage/commands/chat.ts +51 -0
- package/src/platforms/imessage/commands/doctor.test.ts +80 -0
- package/src/platforms/imessage/commands/doctor.ts +139 -0
- package/src/platforms/imessage/commands/index.ts +6 -0
- package/src/platforms/imessage/commands/message.test.ts +20 -0
- package/src/platforms/imessage/commands/message.ts +151 -0
- package/src/platforms/imessage/commands/setup.test.ts +52 -0
- package/src/platforms/imessage/commands/setup.ts +76 -0
- package/src/platforms/imessage/commands/shared.test.ts +19 -0
- package/src/platforms/imessage/commands/shared.ts +69 -0
- package/src/platforms/imessage/commands/whoami.ts +22 -0
- package/src/platforms/imessage/credential-manager.test.ts +122 -0
- package/src/platforms/imessage/credential-manager.ts +123 -0
- package/src/platforms/imessage/ensure-auth.ts +18 -0
- package/src/platforms/imessage/errors.ts +27 -0
- package/src/platforms/imessage/index.test.ts +13 -0
- package/src/platforms/imessage/index.ts +16 -0
- package/src/platforms/imessage/rpc.ts +157 -0
- package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
- package/src/platforms/imessage/types.test.ts +47 -0
- package/src/platforms/imessage/types.ts +95 -0
- package/src/platforms/instagram/client.test.ts +478 -33
- package/src/platforms/instagram/client.ts +472 -68
- package/src/platforms/instagram/commands/auth.test.ts +177 -1
- package/src/platforms/instagram/commands/auth.ts +182 -11
- package/src/platforms/instagram/credential-manager.test.ts +30 -0
- package/src/platforms/instagram/credential-manager.ts +23 -1
- package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
- package/src/platforms/instagram/hybrid-listener.ts +196 -0
- package/src/platforms/instagram/index.ts +10 -0
- package/src/platforms/instagram/mqtt/connection.ts +72 -0
- package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
- package/src/platforms/instagram/mqtt/thrift.ts +159 -0
- package/src/platforms/instagram/mqtt/transport.ts +285 -0
- package/src/platforms/instagram/realtime-listener.test.ts +34 -0
- package/src/platforms/instagram/realtime-listener.ts +201 -0
- package/src/platforms/instagram/types.ts +11 -8
- package/src/platforms/teams/app-config.ts +92 -0
- package/src/platforms/teams/client.test.ts +335 -1
- package/src/platforms/teams/client.ts +342 -7
- package/src/platforms/teams/commands/auth.test.ts +93 -1
- package/src/platforms/teams/commands/auth.ts +182 -3
- package/src/platforms/teams/commands/file.test.ts +92 -1
- package/src/platforms/teams/commands/file.ts +76 -1
- package/src/platforms/teams/commands/message.test.ts +95 -1
- package/src/platforms/teams/commands/message.ts +145 -3
- package/src/platforms/teams/credential-manager.test.ts +20 -0
- package/src/platforms/teams/credential-manager.ts +68 -3
- package/src/platforms/teams/device-code.test.ts +311 -0
- package/src/platforms/teams/device-code.ts +277 -0
- package/src/platforms/teams/device-login.test.ts +288 -0
- package/src/platforms/teams/device-login.ts +206 -0
- package/src/platforms/teams/ensure-auth.ts +11 -0
- package/src/platforms/teams/index.test.ts +10 -0
- package/src/platforms/teams/index.ts +16 -1
- package/src/platforms/teams/listener.ts +295 -0
- package/src/platforms/teams/realm-discovery.test.ts +73 -0
- package/src/platforms/teams/realm-discovery.ts +42 -0
- package/src/platforms/teams/token-extractor.ts +69 -0
- package/src/platforms/teams/token-provider.test.ts +227 -0
- package/src/platforms/teams/token-provider.ts +284 -0
- package/src/platforms/teams/trouter.test.ts +114 -0
- package/src/platforms/teams/trouter.ts +228 -0
- package/src/platforms/teams/types.test.ts +17 -0
- package/src/platforms/teams/types.ts +86 -0
- package/src/platforms/webexbot/cli.ts +0 -0
- package/src/tui/adapters/imessage-adapter.ts +131 -0
- package/src/tui/adapters/instagram-adapter.ts +13 -0
- package/src/tui/adapters/teams-adapter.ts +23 -0
- package/src/tui/app.ts +16 -3
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events'
|
|
2
|
+
import tls from 'node:tls'
|
|
3
|
+
|
|
4
|
+
const MQTTOT_HOST = 'edge-mqtt.facebook.com'
|
|
5
|
+
const MQTTOT_PORT = 443
|
|
6
|
+
const PROTOCOL_NAME = 'MQTToT'
|
|
7
|
+
const PROTOCOL_LEVEL = 3
|
|
8
|
+
|
|
9
|
+
// CONNECT flags 0xC2 = username present + clean session; the "password" travels
|
|
10
|
+
// inside the Thrift payload rather than the standard MQTT password field.
|
|
11
|
+
const CONNECT_FLAGS = 0xc2
|
|
12
|
+
const KEEPALIVE_SECONDS = 60
|
|
13
|
+
const CONNACK_TIMEOUT_MS = 15_000
|
|
14
|
+
|
|
15
|
+
const PacketType = {
|
|
16
|
+
Connect: 1,
|
|
17
|
+
ConnAck: 2,
|
|
18
|
+
Publish: 3,
|
|
19
|
+
PubAck: 4,
|
|
20
|
+
Subscribe: 8,
|
|
21
|
+
PingReq: 12,
|
|
22
|
+
PingResp: 13,
|
|
23
|
+
Disconnect: 14,
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
export interface MqttPublish {
|
|
27
|
+
topic: string
|
|
28
|
+
payload: Buffer
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MqttTransportEventMap {
|
|
32
|
+
connect: []
|
|
33
|
+
publish: [MqttPublish]
|
|
34
|
+
error: [Error]
|
|
35
|
+
close: []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type EventKey = keyof MqttTransportEventMap
|
|
39
|
+
|
|
40
|
+
export class MqttTransport {
|
|
41
|
+
private socket: tls.TLSSocket | null = null
|
|
42
|
+
private emitter = new EventEmitter()
|
|
43
|
+
private buffer = Buffer.alloc(0)
|
|
44
|
+
private nextPacketId = 1
|
|
45
|
+
private pingTimer: ReturnType<typeof setInterval> | null = null
|
|
46
|
+
private connackTimer: ReturnType<typeof setTimeout> | null = null
|
|
47
|
+
private connackTimeoutMs: number
|
|
48
|
+
|
|
49
|
+
constructor(options: { connackTimeoutMs?: number } = {}) {
|
|
50
|
+
this.connackTimeoutMs = options.connackTimeoutMs ?? CONNACK_TIMEOUT_MS
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
connect(connectPayload: Buffer): void {
|
|
54
|
+
const socket = tls.connect({ host: MQTTOT_HOST, port: MQTTOT_PORT, servername: MQTTOT_HOST }, () => {
|
|
55
|
+
socket.write(this.buildConnectPacket(connectPayload))
|
|
56
|
+
})
|
|
57
|
+
this.socket = socket
|
|
58
|
+
|
|
59
|
+
// Guard against a peer that accepts TLS but never sends CONNACK, which would
|
|
60
|
+
// otherwise leave a hybrid caller waiting forever instead of failing over.
|
|
61
|
+
this.connackTimer = setTimeout(() => {
|
|
62
|
+
this.connackTimer = null
|
|
63
|
+
this.emitter.emit('error', new Error('MQTToT CONNACK timed out'))
|
|
64
|
+
socket.destroy()
|
|
65
|
+
}, this.connackTimeoutMs)
|
|
66
|
+
|
|
67
|
+
socket.on('data', (chunk: Buffer) => this.onData(chunk))
|
|
68
|
+
socket.on('error', (err: Error) => this.emitter.emit('error', err))
|
|
69
|
+
socket.on('close', () => {
|
|
70
|
+
this.clearConnackTimer()
|
|
71
|
+
this.stopPing()
|
|
72
|
+
this.emitter.emit('close')
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
publish(topic: string, payload: Buffer, qos: 0 | 1 = 1): void {
|
|
77
|
+
if (!this.socket) throw new Error('MQTT transport not connected')
|
|
78
|
+
this.socket.write(this.buildPublishPacket(topic, payload, qos))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
subscribe(topics: string[]): void {
|
|
82
|
+
if (!this.socket) throw new Error('MQTT transport not connected')
|
|
83
|
+
this.socket.write(this.buildSubscribePacket(topics))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
disconnect(): void {
|
|
87
|
+
this.clearConnackTimer()
|
|
88
|
+
this.stopPing()
|
|
89
|
+
if (this.socket) {
|
|
90
|
+
try {
|
|
91
|
+
this.socket.write(Buffer.from([PacketType.Disconnect << 4, 0]))
|
|
92
|
+
} catch {
|
|
93
|
+
// socket may already be torn down; closing below is sufficient
|
|
94
|
+
}
|
|
95
|
+
this.socket.destroy()
|
|
96
|
+
this.socket = null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
on<K extends EventKey>(event: K, listener: (...args: MqttTransportEventMap[K]) => void): this {
|
|
101
|
+
this.emitter.on(event, listener as (...args: unknown[]) => void)
|
|
102
|
+
return this
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private onData(chunk: Buffer): void {
|
|
106
|
+
this.buffer = Buffer.concat([this.buffer, chunk])
|
|
107
|
+
this.drainPackets()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private drainPackets(): void {
|
|
111
|
+
while (this.buffer.length >= 2) {
|
|
112
|
+
const decoded = decodeRemainingLength(this.buffer, 1)
|
|
113
|
+
if (!decoded) return
|
|
114
|
+
|
|
115
|
+
const { length: remainingLength, bytesUsed } = decoded
|
|
116
|
+
const totalLength = 1 + bytesUsed + remainingLength
|
|
117
|
+
if (this.buffer.length < totalLength) return
|
|
118
|
+
|
|
119
|
+
const packet = this.buffer.subarray(0, totalLength)
|
|
120
|
+
this.buffer = this.buffer.subarray(totalLength)
|
|
121
|
+
this.handlePacket(packet, 1 + bytesUsed)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private handlePacket(packet: Buffer, bodyStart: number): void {
|
|
126
|
+
const type = (packet[0]! >> 4) & 0x0f
|
|
127
|
+
|
|
128
|
+
switch (type) {
|
|
129
|
+
case PacketType.ConnAck: {
|
|
130
|
+
this.clearConnackTimer()
|
|
131
|
+
const returnCode = packet[bodyStart + 1]
|
|
132
|
+
if (returnCode === 0) {
|
|
133
|
+
this.startPing()
|
|
134
|
+
this.emitter.emit('connect')
|
|
135
|
+
} else {
|
|
136
|
+
this.emitter.emit('error', new Error(`MQTToT CONNACK refused with return code ${returnCode}`))
|
|
137
|
+
}
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
case PacketType.Publish:
|
|
141
|
+
this.handlePublish(packet, bodyStart)
|
|
142
|
+
break
|
|
143
|
+
case PacketType.PingResp:
|
|
144
|
+
case PacketType.PubAck:
|
|
145
|
+
case PacketType.Subscribe + 1:
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private handlePublish(packet: Buffer, bodyStart: number): void {
|
|
151
|
+
const qos = (packet[0]! >> 1) & 0x03
|
|
152
|
+
|
|
153
|
+
// A truncated/malformed frame must not throw out of the data handler, or it
|
|
154
|
+
// would crash the process instead of letting the listener fail over.
|
|
155
|
+
if (bodyStart + 2 > packet.length) return
|
|
156
|
+
const topicLength = packet.readUInt16BE(bodyStart)
|
|
157
|
+
let offset = bodyStart + 2
|
|
158
|
+
if (offset + topicLength > packet.length) return
|
|
159
|
+
const topic = packet.toString('utf8', offset, offset + topicLength)
|
|
160
|
+
offset += topicLength
|
|
161
|
+
|
|
162
|
+
let packetId: number | null = null
|
|
163
|
+
if (qos > 0) {
|
|
164
|
+
if (offset + 2 > packet.length) return
|
|
165
|
+
packetId = packet.readUInt16BE(offset)
|
|
166
|
+
offset += 2
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const payload = packet.subarray(offset)
|
|
170
|
+
this.emitter.emit('publish', { topic, payload })
|
|
171
|
+
|
|
172
|
+
if (qos === 1 && packetId !== null) {
|
|
173
|
+
this.sendPubAck(packetId)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private sendPubAck(packetId: number): void {
|
|
178
|
+
const packet = Buffer.from([PacketType.PubAck << 4, 2, (packetId >> 8) & 0xff, packetId & 0xff])
|
|
179
|
+
this.socket?.write(packet)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private startPing(): void {
|
|
183
|
+
this.stopPing()
|
|
184
|
+
this.pingTimer = setInterval(() => {
|
|
185
|
+
this.socket?.write(Buffer.from([PacketType.PingReq << 4, 0]))
|
|
186
|
+
}, KEEPALIVE_SECONDS * 1000)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private stopPing(): void {
|
|
190
|
+
if (this.pingTimer) {
|
|
191
|
+
clearInterval(this.pingTimer)
|
|
192
|
+
this.pingTimer = null
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private clearConnackTimer(): void {
|
|
197
|
+
if (this.connackTimer) {
|
|
198
|
+
clearTimeout(this.connackTimer)
|
|
199
|
+
this.connackTimer = null
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private buildConnectPacket(payload: Buffer): Buffer {
|
|
204
|
+
const variableHeader: number[] = []
|
|
205
|
+
appendString(variableHeader, PROTOCOL_NAME)
|
|
206
|
+
variableHeader.push(PROTOCOL_LEVEL, CONNECT_FLAGS)
|
|
207
|
+
variableHeader.push((KEEPALIVE_SECONDS >> 8) & 0xff, KEEPALIVE_SECONDS & 0xff)
|
|
208
|
+
|
|
209
|
+
const body = Buffer.concat([Buffer.from(variableHeader), payload])
|
|
210
|
+
return Buffer.concat([Buffer.from([PacketType.Connect << 4, ...encodeRemainingLength(body.length)]), body])
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private buildPublishPacket(topic: string, payload: Buffer, qos: 0 | 1): Buffer {
|
|
214
|
+
const variableHeader: number[] = []
|
|
215
|
+
appendString(variableHeader, topic)
|
|
216
|
+
if (qos > 0) {
|
|
217
|
+
const packetId = this.allocatePacketId()
|
|
218
|
+
variableHeader.push((packetId >> 8) & 0xff, packetId & 0xff)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const body = Buffer.concat([Buffer.from(variableHeader), payload])
|
|
222
|
+
const flags = qos << 1
|
|
223
|
+
return Buffer.concat([
|
|
224
|
+
Buffer.from([(PacketType.Publish << 4) | flags, ...encodeRemainingLength(body.length)]),
|
|
225
|
+
body,
|
|
226
|
+
])
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private buildSubscribePacket(topics: string[]): Buffer {
|
|
230
|
+
const packetId = this.allocatePacketId()
|
|
231
|
+
const body: number[] = [(packetId >> 8) & 0xff, packetId & 0xff]
|
|
232
|
+
for (const topic of topics) {
|
|
233
|
+
appendString(body, topic)
|
|
234
|
+
body.push(0)
|
|
235
|
+
}
|
|
236
|
+
// SUBSCRIBE has reserved flags 0010 in the fixed-header low nibble.
|
|
237
|
+
return Buffer.concat([
|
|
238
|
+
Buffer.from([(PacketType.Subscribe << 4) | 0x02, ...encodeRemainingLength(body.length)]),
|
|
239
|
+
Buffer.from(body),
|
|
240
|
+
])
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private allocatePacketId(): number {
|
|
244
|
+
const id = this.nextPacketId
|
|
245
|
+
this.nextPacketId = (this.nextPacketId % 0xffff) + 1
|
|
246
|
+
return id
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function appendString(target: number[], value: string): void {
|
|
251
|
+
const buf = Buffer.from(value, 'utf8')
|
|
252
|
+
target.push((buf.length >> 8) & 0xff, buf.length & 0xff)
|
|
253
|
+
for (const b of buf) target.push(b)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function encodeRemainingLength(length: number): number[] {
|
|
257
|
+
const bytes: number[] = []
|
|
258
|
+
let value = length
|
|
259
|
+
do {
|
|
260
|
+
let digit = value & 0x7f
|
|
261
|
+
value >>= 7
|
|
262
|
+
if (value > 0) digit |= 0x80
|
|
263
|
+
bytes.push(digit)
|
|
264
|
+
} while (value > 0)
|
|
265
|
+
return bytes
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function decodeRemainingLength(buffer: Buffer, offset: number): { length: number; bytesUsed: number } | null {
|
|
269
|
+
let length = 0
|
|
270
|
+
let multiplier = 1
|
|
271
|
+
let bytesUsed = 0
|
|
272
|
+
|
|
273
|
+
while (offset + bytesUsed < buffer.length) {
|
|
274
|
+
const byte = buffer[offset + bytesUsed]!
|
|
275
|
+
length += (byte & 0x7f) * multiplier
|
|
276
|
+
bytesUsed++
|
|
277
|
+
if ((byte & 0x80) === 0) {
|
|
278
|
+
return { length, bytesUsed }
|
|
279
|
+
}
|
|
280
|
+
multiplier *= 128
|
|
281
|
+
if (bytesUsed > 4) return null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { InstagramRealtimeListener } from '@/platforms/instagram/realtime-listener'
|
|
4
|
+
|
|
5
|
+
function makeClient(overrides: Record<string, unknown> = {}) {
|
|
6
|
+
return {
|
|
7
|
+
getUserId: () => '123',
|
|
8
|
+
getSessionState: () => ({}) as never,
|
|
9
|
+
fetchIrisBootstrap: async () => ({ seqId: 1, snapshotAtMs: 1 }),
|
|
10
|
+
...overrides,
|
|
11
|
+
} as never
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('InstagramRealtimeListener', () => {
|
|
15
|
+
it('stays restartable after a bootstrap failure', async () => {
|
|
16
|
+
let calls = 0
|
|
17
|
+
const client = makeClient({
|
|
18
|
+
fetchIrisBootstrap: async () => {
|
|
19
|
+
calls++
|
|
20
|
+
throw new Error('bootstrap failed')
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
const listener = new InstagramRealtimeListener(client)
|
|
24
|
+
|
|
25
|
+
// given a failing bootstrap, when start() rejects, then running must be reset
|
|
26
|
+
await expect(listener.start()).rejects.toThrow('bootstrap failed')
|
|
27
|
+
|
|
28
|
+
// when start() is called again, then it must retry rather than no-op at the guard
|
|
29
|
+
await expect(listener.start()).rejects.toThrow('bootstrap failed')
|
|
30
|
+
expect(calls).toBe(2)
|
|
31
|
+
|
|
32
|
+
listener.stop()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events'
|
|
2
|
+
import { deflateSync, inflateSync } from 'node:zlib'
|
|
3
|
+
|
|
4
|
+
import type { InstagramClient } from './client'
|
|
5
|
+
import { buildConnectPayload } from './mqtt/connection'
|
|
6
|
+
import { MqttTransport } from './mqtt/transport'
|
|
7
|
+
import { extractMediaUrl, extractMessageText, getMessageType, type InstagramMessageSummary } from './types'
|
|
8
|
+
|
|
9
|
+
export interface InstagramRealtimeListenerEventMap {
|
|
10
|
+
message: [InstagramMessageSummary]
|
|
11
|
+
error: [Error]
|
|
12
|
+
connected: [{ userId: string }]
|
|
13
|
+
disconnected: []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface InstagramRealtimeListenerOptions {
|
|
17
|
+
connackTimeoutMs?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type EventKey = keyof InstagramRealtimeListenerEventMap
|
|
21
|
+
|
|
22
|
+
const TOPIC_PUBSUB = '88'
|
|
23
|
+
const TOPIC_REALTIME_SUB = '149'
|
|
24
|
+
const TOPIC_IRIS_SUB = '134'
|
|
25
|
+
const TOPIC_MESSAGE_SYNC = '146'
|
|
26
|
+
|
|
27
|
+
const ZLIB_MAGIC_BYTE = 0x78
|
|
28
|
+
|
|
29
|
+
const DM_THREAD_PATH_PATTERN = /^\/direct_v2\/(?:inbox\/)?threads\//
|
|
30
|
+
const THREAD_ID_PATTERN = /^\/direct_v2\/(?:inbox\/)?threads\/(\d+)/
|
|
31
|
+
|
|
32
|
+
interface IrisEnvelope {
|
|
33
|
+
data?: IrisPatch[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface IrisPatch {
|
|
37
|
+
op?: string
|
|
38
|
+
path?: string
|
|
39
|
+
value?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class InstagramRealtimeListener {
|
|
43
|
+
private client: InstagramClient
|
|
44
|
+
private transport: MqttTransport | null = null
|
|
45
|
+
private emitter = new EventEmitter()
|
|
46
|
+
private running = false
|
|
47
|
+
private userId: string
|
|
48
|
+
private connackTimeoutMs: number | undefined
|
|
49
|
+
|
|
50
|
+
constructor(client: InstagramClient, options: InstagramRealtimeListenerOptions = {}) {
|
|
51
|
+
this.client = client
|
|
52
|
+
this.userId = client.getUserId() ?? ''
|
|
53
|
+
this.connackTimeoutMs = options.connackTimeoutMs
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async start(): Promise<void> {
|
|
57
|
+
if (this.running) return
|
|
58
|
+
this.running = true
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const session = this.client.getSessionState()
|
|
62
|
+
const iris = await this.client.fetchIrisBootstrap()
|
|
63
|
+
|
|
64
|
+
if (!this.running) return
|
|
65
|
+
|
|
66
|
+
const connectPayload = buildConnectPayload(session)
|
|
67
|
+
|
|
68
|
+
const transport = new MqttTransport({ connackTimeoutMs: this.connackTimeoutMs })
|
|
69
|
+
this.transport = transport
|
|
70
|
+
|
|
71
|
+
transport.on('connect', () => {
|
|
72
|
+
this.sendSubscriptions(iris)
|
|
73
|
+
this.emitter.emit('connected', { userId: this.userId })
|
|
74
|
+
})
|
|
75
|
+
transport.on('publish', ({ topic, payload }) => this.handlePublish(topic, payload))
|
|
76
|
+
transport.on('error', (err) => this.emitter.emit('error', err))
|
|
77
|
+
transport.on('close', () => {
|
|
78
|
+
if (this.running) this.emitter.emit('disconnected')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
transport.connect(connectPayload)
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Any setup failure must leave the listener cleanly restartable: reset running
|
|
84
|
+
// and discard the partial transport so a later start() does not no-op at the guard.
|
|
85
|
+
this.running = false
|
|
86
|
+
if (this.transport) {
|
|
87
|
+
this.transport.disconnect()
|
|
88
|
+
this.transport = null
|
|
89
|
+
}
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
stop(): void {
|
|
95
|
+
this.running = false
|
|
96
|
+
if (this.transport) {
|
|
97
|
+
this.transport.disconnect()
|
|
98
|
+
this.transport = null
|
|
99
|
+
}
|
|
100
|
+
this.emitter.emit('disconnected')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
on<K extends EventKey>(event: K, listener: (...args: InstagramRealtimeListenerEventMap[K]) => void): this {
|
|
104
|
+
this.emitter.on(event, listener as (...args: unknown[]) => void)
|
|
105
|
+
return this
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
off<K extends EventKey>(event: K, listener: (...args: InstagramRealtimeListenerEventMap[K]) => void): this {
|
|
109
|
+
this.emitter.off(event, listener as (...args: unknown[]) => void)
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
once<K extends EventKey>(event: K, listener: (...args: InstagramRealtimeListenerEventMap[K]) => void): this {
|
|
114
|
+
this.emitter.once(event, listener as (...args: unknown[]) => void)
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private sendSubscriptions(iris: { seqId: number; snapshotAtMs: number }): void {
|
|
119
|
+
const transport = this.transport
|
|
120
|
+
if (!transport) return
|
|
121
|
+
|
|
122
|
+
transport.publish(
|
|
123
|
+
TOPIC_IRIS_SUB,
|
|
124
|
+
deflateJson({
|
|
125
|
+
seq_id: iris.seqId,
|
|
126
|
+
snapshot_at_ms: iris.snapshotAtMs,
|
|
127
|
+
snapshot_app_version: APP_VERSION,
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
transport.publish(TOPIC_PUBSUB, deflateJson({ sub: [`ig/u/v1/${this.userId}`] }))
|
|
132
|
+
|
|
133
|
+
transport.publish(
|
|
134
|
+
TOPIC_REALTIME_SUB,
|
|
135
|
+
deflateJson({
|
|
136
|
+
sub: [`1/graphqlsubscriptions/17867973967082385/{"input_data":{"user_id":"${this.userId}"}}`],
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private handlePublish(topic: string, payload: Buffer): void {
|
|
142
|
+
if (topic !== TOPIC_MESSAGE_SYNC) return
|
|
143
|
+
|
|
144
|
+
let envelopes: IrisEnvelope[]
|
|
145
|
+
try {
|
|
146
|
+
envelopes = JSON.parse(inflateIfNeeded(payload).toString('utf8')) as IrisEnvelope[]
|
|
147
|
+
} catch (error) {
|
|
148
|
+
this.emitter.emit('error', error instanceof Error ? error : new Error(String(error)))
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const envelope of envelopes) {
|
|
153
|
+
for (const patch of envelope.data ?? []) {
|
|
154
|
+
const message = this.decodeDmPatch(patch)
|
|
155
|
+
if (message) this.emitter.emit('message', message)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private decodeDmPatch(patch: IrisPatch): InstagramMessageSummary | null {
|
|
161
|
+
if (patch.op !== 'add') return null
|
|
162
|
+
if (!patch.path || !patch.value || !DM_THREAD_PATH_PATTERN.test(patch.path)) return null
|
|
163
|
+
|
|
164
|
+
const threadId = patch.path.match(THREAD_ID_PATTERN)?.[1] ?? ''
|
|
165
|
+
|
|
166
|
+
let item: Record<string, unknown>
|
|
167
|
+
try {
|
|
168
|
+
item = JSON.parse(patch.value) as Record<string, unknown>
|
|
169
|
+
} catch {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const itemId = String(item['item_id'] ?? '')
|
|
174
|
+
const fromUserId = String(item['user_id'] ?? '')
|
|
175
|
+
const timestampUs = item['timestamp'] as number | string | undefined
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
id: itemId,
|
|
179
|
+
thread_id: threadId,
|
|
180
|
+
from: fromUserId,
|
|
181
|
+
timestamp: timestampUs ? new Date(Number(timestampUs) / 1000).toISOString() : new Date().toISOString(),
|
|
182
|
+
is_outgoing: fromUserId === this.userId,
|
|
183
|
+
type: getMessageType(item),
|
|
184
|
+
text: extractMessageText(item),
|
|
185
|
+
media_url: extractMediaUrl(item),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const APP_VERSION = '312.1.0.34.111'
|
|
191
|
+
|
|
192
|
+
function deflateJson(value: unknown): Buffer {
|
|
193
|
+
return deflateSync(JSON.stringify(value), { level: 9 })
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function inflateIfNeeded(payload: Buffer): Buffer {
|
|
197
|
+
if (payload.length > 0 && payload[0] === ZLIB_MAGIC_BYTE) {
|
|
198
|
+
return inflateSync(payload)
|
|
199
|
+
}
|
|
200
|
+
return payload
|
|
201
|
+
}
|
|
@@ -60,19 +60,22 @@ export interface InstagramMessageSummary {
|
|
|
60
60
|
media_url?: string
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export interface InstagramDevice {
|
|
64
|
+
phone_id: string
|
|
65
|
+
uuid: string
|
|
66
|
+
android_device_id: string
|
|
67
|
+
advertising_id: string
|
|
68
|
+
client_session_id: string
|
|
69
|
+
device_string: string
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
export interface InstagramSessionState {
|
|
64
73
|
cookies: string
|
|
65
|
-
device:
|
|
66
|
-
phone_id: string
|
|
67
|
-
uuid: string
|
|
68
|
-
android_device_id: string
|
|
69
|
-
advertising_id: string
|
|
70
|
-
client_session_id: string
|
|
71
|
-
device_string: string
|
|
72
|
-
}
|
|
74
|
+
device: InstagramDevice
|
|
73
75
|
authorization?: string
|
|
74
76
|
user_id?: string
|
|
75
77
|
mid?: string
|
|
78
|
+
www_claim?: string
|
|
76
79
|
challenge_path?: string
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { TeamsAccountType } from './types'
|
|
2
|
+
|
|
3
|
+
export const TEAMS_DESKTOP_CLIENT_ID = '1fec8e78-bce4-4aaf-ab1b-5451cc387264'
|
|
4
|
+
export const TEAMS_WEB_CLIENT_ID = '5e3ce6c0-2b1f-4285-8d4b-75ee78787346'
|
|
5
|
+
|
|
6
|
+
export const CONSUMER_TENANT_ID = '9188040d-6c67-4c5b-b112-36a304b66dad'
|
|
7
|
+
// The Microsoft Services (MSA passthrough) tenant. Consumer/MSA tokens for the
|
|
8
|
+
// Skype resource carry this `tid`, so it is a placeholder that still requires
|
|
9
|
+
// tenant discovery before the skype-token exchange.
|
|
10
|
+
export const MICROSOFT_SERVICES_TENANT_ID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a'
|
|
11
|
+
export const WORK_TENANT_ID = 'organizations'
|
|
12
|
+
|
|
13
|
+
export const DEVICE_CODE_SCOPE_TEAMS = 'service::api.fl.teams.microsoft.com::MBI_SSL openid profile offline_access'
|
|
14
|
+
export const DEVICE_CODE_SCOPE_SKYPE = 'service::api.fl.spaces.skype.com::MBI_SSL openid profile offline_access'
|
|
15
|
+
export const WORK_DEVICE_CODE_SCOPE_TEAMS = 'https://api.spaces.skype.com/.default openid profile offline_access'
|
|
16
|
+
export const WORK_DEVICE_CODE_SCOPE_SKYPE = 'https://api.spaces.skype.com/.default openid profile offline_access'
|
|
17
|
+
|
|
18
|
+
export const SUBSTRATE_SEARCH_URL = 'https://substrate.office.com/searchservice/api/v2/query'
|
|
19
|
+
export const TEAMS_WEB_ORIGIN = 'https://teams.microsoft.com'
|
|
20
|
+
export const AAD_SCOPE_SUBSTRATE = 'https://substrate.office.com/.default offline_access'
|
|
21
|
+
export const AAD_SCOPE_GRAPH = 'https://graph.microsoft.com/.default offline_access'
|
|
22
|
+
export const AAD_AUDIENCE_SUBSTRATE = 'https://substrate.office.com'
|
|
23
|
+
export const AAD_AUDIENCE_GRAPH = 'https://graph.microsoft.com'
|
|
24
|
+
|
|
25
|
+
export const AUTHZ_CONSUMER_URL = 'https://teams.live.com/api/auth/v1.0/authz/consumer'
|
|
26
|
+
export const AUTHZ_WORK_URL = 'https://teams.microsoft.com/api/authsvc/v1.0/authz'
|
|
27
|
+
|
|
28
|
+
// Control-plane tenant-discovery endpoint. The region segment is not
|
|
29
|
+
// authoritative for this call (it lists every tenant the user belongs to
|
|
30
|
+
// regardless of home region), so we pin the value fossteams uses.
|
|
31
|
+
export const TEAMS_TENANTS_URL = 'https://teams.microsoft.com/api/mt/emea/beta/users/tenants'
|
|
32
|
+
|
|
33
|
+
export const GET_CREDENTIAL_TYPE_URL = 'https://login.microsoftonline.com/common/GetCredentialType'
|
|
34
|
+
|
|
35
|
+
export function consumerDeviceCodeUrl(): string {
|
|
36
|
+
return `https://login.microsoftonline.com/${CONSUMER_TENANT_ID}/oauth2/v2.0/devicecode`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function consumerTokenUrl(): string {
|
|
40
|
+
return `https://login.microsoftonline.com/${CONSUMER_TENANT_ID}/oauth2/v2.0/token`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function organizationsDeviceCodeUrl(): string {
|
|
44
|
+
return `https://login.microsoftonline.com/${WORK_TENANT_ID}/oauth2/v2.0/devicecode`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function organizationsTokenUrl(): string {
|
|
48
|
+
return `https://login.microsoftonline.com/${WORK_TENANT_ID}/oauth2/v2.0/token`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function tenantTokenUrl(tenantId: string): string {
|
|
52
|
+
return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function deviceCodeUrl(accountType: TeamsAccountType): string {
|
|
56
|
+
return accountType === 'personal' ? consumerDeviceCodeUrl() : organizationsDeviceCodeUrl()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function deviceCodeTokenUrl(accountType: TeamsAccountType, tenantId?: string): string {
|
|
60
|
+
if (tenantId) return tenantTokenUrl(tenantId)
|
|
61
|
+
return accountType === 'personal' ? consumerTokenUrl() : organizationsTokenUrl()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function deviceCodeTeamsScope(accountType: TeamsAccountType): string {
|
|
65
|
+
return accountType === 'personal' ? DEVICE_CODE_SCOPE_TEAMS : WORK_DEVICE_CODE_SCOPE_TEAMS
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function deviceCodeSkypeScope(accountType: TeamsAccountType): string {
|
|
69
|
+
return accountType === 'personal' ? DEVICE_CODE_SCOPE_SKYPE : WORK_DEVICE_CODE_SCOPE_SKYPE
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function authzUrl(accountType: TeamsAccountType): string {
|
|
73
|
+
return accountType === 'personal' ? AUTHZ_CONSUMER_URL : AUTHZ_WORK_URL
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface TeamsAppClient {
|
|
77
|
+
clientId: string
|
|
78
|
+
source: 'env' | 'builtin'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseTrimmed(value: string | undefined): string | undefined {
|
|
82
|
+
const normalized = value?.trim()
|
|
83
|
+
return normalized ? normalized : undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getTeamsAppClientId(accountType: TeamsAccountType = 'personal'): TeamsAppClient {
|
|
87
|
+
const envClientId = parseTrimmed(process.env.AGENT_TEAMS_CLIENT_ID)
|
|
88
|
+
if (envClientId) {
|
|
89
|
+
return { clientId: envClientId, source: 'env' }
|
|
90
|
+
}
|
|
91
|
+
return { clientId: accountType === 'work' ? TEAMS_DESKTOP_CLIENT_ID : TEAMS_WEB_CLIENT_ID, source: 'builtin' }
|
|
92
|
+
}
|