@2en/clawly-plugins 1.32.0-beta.0 → 1.32.0-beta.1
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/gateway/index.ts +2 -0
- package/gateway/weixin-login.ts +130 -0
- package/package.json +1 -1
package/gateway/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {registerPresence} from './presence'
|
|
|
23
23
|
import {registerSessionSanitize} from './session-sanitize'
|
|
24
24
|
import {registerUploadHttpRoute} from '../http/file/upload'
|
|
25
25
|
import {registerVersion} from './version'
|
|
26
|
+
import {registerWeixinLogin} from './weixin-login'
|
|
26
27
|
|
|
27
28
|
export function registerGateway(api: PluginApi) {
|
|
28
29
|
// Initialize OTel logs provider (no-op if plugin telemetry config is missing)
|
|
@@ -72,4 +73,5 @@ export function registerGateway(api: PluginApi) {
|
|
|
72
73
|
registerUploadHttpRoute(api)
|
|
73
74
|
registerAudit(api)
|
|
74
75
|
registerCalendarNative(api)
|
|
76
|
+
registerWeixinLogin(api)
|
|
75
77
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat (openclaw-weixin) QR login bridge.
|
|
3
|
+
*
|
|
4
|
+
* The official @tencent-weixin/openclaw-weixin plugin registers as a ChannelPlugin
|
|
5
|
+
* with loginWithQrStart/loginWithQrWait on its gateway adapter, but does NOT
|
|
6
|
+
* register as the `web.login.start` provider (WhatsApp owns that slot).
|
|
7
|
+
*
|
|
8
|
+
* These two gateway methods expose the WeChat plugin's login flow to the mobile app:
|
|
9
|
+
* clawly.weixin.login.start → { qrDataUrl, message }
|
|
10
|
+
* clawly.weixin.login.wait → { connected, message }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {PluginApi} from '../types'
|
|
14
|
+
|
|
15
|
+
const WEIXIN_CHANNEL_ID = 'openclaw-weixin'
|
|
16
|
+
const GATEWAY_TIMEOUT_MS = 60_000
|
|
17
|
+
|
|
18
|
+
/** Race a promise against a timeout, clearing the timer on settle. */
|
|
19
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
20
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
21
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
22
|
+
timeoutId = setTimeout(() => reject(new Error(`${label} timed out`)), ms)
|
|
23
|
+
})
|
|
24
|
+
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ChannelPluginLike = {
|
|
28
|
+
id: string
|
|
29
|
+
gateway?: {
|
|
30
|
+
loginWithQrStart?: (params: {
|
|
31
|
+
accountId?: string
|
|
32
|
+
force?: boolean
|
|
33
|
+
timeoutMs?: number
|
|
34
|
+
verbose?: boolean
|
|
35
|
+
}) => Promise<{qrDataUrl?: string; message: string}>
|
|
36
|
+
loginWithQrWait?: (params: {
|
|
37
|
+
accountId?: string
|
|
38
|
+
timeoutMs?: number
|
|
39
|
+
}) => Promise<{connected: boolean; message: string}>
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the openclaw-weixin channel plugin from the host OpenClaw process.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ KNOWN INTERNAL DEPENDENCY: This uses OpenClaw's private module path
|
|
47
|
+
* (`openclaw/src/channels/plugins/index.js`) because there is no public API
|
|
48
|
+
* for accessing the channel plugin registry. If OpenClaw restructures this
|
|
49
|
+
* path, the require will fail and `resolveWeixinPlugin` returns null (login
|
|
50
|
+
* unavailable, not a crash). Verify this path when upgrading OpenClaw.
|
|
51
|
+
*/
|
|
52
|
+
function resolveWeixinPlugin(logger?: {warn: (msg: string) => void}): ChannelPluginLike | null {
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
|
+
const {getChannelPlugin} = require('openclaw/src/channels/plugins/index.js')
|
|
56
|
+
const plugin = getChannelPlugin(WEIXIN_CHANNEL_ID) as ChannelPluginLike | undefined
|
|
57
|
+
return plugin ?? null
|
|
58
|
+
} catch (err) {
|
|
59
|
+
logger?.warn(
|
|
60
|
+
`weixin-login: failed to resolve channel plugin registry: ${err instanceof Error ? err.message : err}`,
|
|
61
|
+
)
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function registerWeixinLogin(api: PluginApi) {
|
|
67
|
+
api.registerGatewayMethod('clawly.weixin.login.start', async ({params, respond}) => {
|
|
68
|
+
const plugin = resolveWeixinPlugin(api.logger)
|
|
69
|
+
if (!plugin?.gateway?.loginWithQrStart) {
|
|
70
|
+
respond(false, undefined, {
|
|
71
|
+
code: 'WEIXIN_PLUGIN_NOT_AVAILABLE',
|
|
72
|
+
message:
|
|
73
|
+
'WeChat plugin is not installed or does not support QR login. ' +
|
|
74
|
+
'Install it with: openclaw plugins install "@tencent-weixin/openclaw-weixin"',
|
|
75
|
+
})
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const p = (params ?? {}) as Record<string, unknown>
|
|
81
|
+
const result = await withTimeout(
|
|
82
|
+
plugin.gateway.loginWithQrStart({
|
|
83
|
+
accountId: p.accountId as string | undefined,
|
|
84
|
+
force: p.force !== undefined ? Boolean(p.force) : undefined,
|
|
85
|
+
timeoutMs: typeof p.timeoutMs === 'number' ? p.timeoutMs : undefined,
|
|
86
|
+
verbose: p.verbose !== undefined ? Boolean(p.verbose) : undefined,
|
|
87
|
+
}),
|
|
88
|
+
GATEWAY_TIMEOUT_MS,
|
|
89
|
+
'loginWithQrStart',
|
|
90
|
+
)
|
|
91
|
+
respond(true, result)
|
|
92
|
+
} catch (err) {
|
|
93
|
+
respond(false, undefined, {
|
|
94
|
+
code: 'WEIXIN_LOGIN_START_ERROR',
|
|
95
|
+
message: err instanceof Error ? err.message : 'Failed to start WeChat QR login',
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
api.registerGatewayMethod('clawly.weixin.login.wait', async ({params, respond}) => {
|
|
101
|
+
const plugin = resolveWeixinPlugin(api.logger)
|
|
102
|
+
if (!plugin?.gateway?.loginWithQrWait) {
|
|
103
|
+
respond(false, undefined, {
|
|
104
|
+
code: 'WEIXIN_PLUGIN_NOT_AVAILABLE',
|
|
105
|
+
message: 'WeChat plugin is not installed or does not support QR login.',
|
|
106
|
+
})
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const p = (params ?? {}) as Record<string, unknown>
|
|
112
|
+
const result = await withTimeout(
|
|
113
|
+
plugin.gateway.loginWithQrWait({
|
|
114
|
+
accountId: p.accountId as string | undefined,
|
|
115
|
+
timeoutMs: typeof p.timeoutMs === 'number' ? p.timeoutMs : undefined,
|
|
116
|
+
}),
|
|
117
|
+
GATEWAY_TIMEOUT_MS,
|
|
118
|
+
'loginWithQrWait',
|
|
119
|
+
)
|
|
120
|
+
respond(true, result)
|
|
121
|
+
} catch (err) {
|
|
122
|
+
respond(false, undefined, {
|
|
123
|
+
code: 'WEIXIN_LOGIN_WAIT_ERROR',
|
|
124
|
+
message: err instanceof Error ? err.message : 'Failed to wait for WeChat QR login',
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
api.logger.info('weixin-login: registered clawly.weixin.login.start/wait methods')
|
|
130
|
+
}
|