openclaw-bitchat 0.1.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/README.md +156 -0
- package/dist/bridge.d.ts +90 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +232 -0
- package/dist/bridge.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +240 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/openclaw.plugin.json +58 -0
- package/package.json +64 -0
- package/src/bridge.ts +284 -0
- package/src/index.ts +288 -0
- package/src/types.ts +155 -0
- package/tsconfig.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @openclaw/bitchat
|
|
2
|
+
|
|
3
|
+
OpenClaw channel plugin for Bitchat BLE mesh network.
|
|
4
|
+
|
|
5
|
+
> ⚠️ **Alpha Software** — This plugin and bitchat-node are experimental. APIs may change without notice.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This plugin enables OpenClaw to communicate via the Bitchat peer-to-peer BLE mesh network. Messages travel directly between devices over Bluetooth Low Energy without any central server.
|
|
10
|
+
|
|
11
|
+
**Requirements:**
|
|
12
|
+
- [bitchat-node](https://github.com/wkyleg/bitchat-node) running as a background service
|
|
13
|
+
- BLE-capable hardware (Mac, Linux with BlueZ, or Windows with BLE support)
|
|
14
|
+
- Other Bitchat peers nearby (iOS app, other nodes)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
openclaw plugins install @openclaw/bitchat
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or for development:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
openclaw plugins install -l /path/to/openclaw-bitchat
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
Add to your OpenClaw config (`~/.openclaw/openclaw.json`):
|
|
31
|
+
|
|
32
|
+
```json5
|
|
33
|
+
{
|
|
34
|
+
channels: {
|
|
35
|
+
bitchat: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
nickname: "my-agent",
|
|
38
|
+
bridgeUrl: "http://localhost:3939",
|
|
39
|
+
dmPolicy: "open", // or "allowlist", "disabled"
|
|
40
|
+
allowFrom: [], // peer IDs when using allowlist
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Configuration Options
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default | Description |
|
|
49
|
+
|--------|------|---------|-------------|
|
|
50
|
+
| `enabled` | boolean | `true` | Enable/disable the channel |
|
|
51
|
+
| `nickname` | string | `"openclaw"` | Display name on the mesh network |
|
|
52
|
+
| `bridgeUrl` | string | `"http://localhost:3939"` | URL of the bitchat-node HTTP bridge |
|
|
53
|
+
| `webhookPath` | string | `"/bitchat-webhook"` | Webhook endpoint for incoming messages |
|
|
54
|
+
| `autoStart` | boolean | `false` | Auto-start bitchat-node daemon |
|
|
55
|
+
| `dmPolicy` | string | `"open"` | DM access policy (`open`, `allowlist`, `disabled`) |
|
|
56
|
+
| `allowFrom` | string[] | `[]` | Allowed peer IDs when using `allowlist` policy |
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### 1. Start bitchat-node
|
|
61
|
+
|
|
62
|
+
First, ensure bitchat-node is running:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd /path/to/bitchat-node
|
|
66
|
+
node dist/bin/bitchat.js --nickname=MyAgent --port=3939
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or run it as a background service.
|
|
70
|
+
|
|
71
|
+
### 2. Configure OpenClaw
|
|
72
|
+
|
|
73
|
+
Add the bitchat channel configuration to your OpenClaw config.
|
|
74
|
+
|
|
75
|
+
### 3. Restart OpenClaw Gateway
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
openclaw gateway restart
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 4. Test
|
|
82
|
+
|
|
83
|
+
Send a message from a Bitchat peer (iOS app or another node). OpenClaw will receive it and route it to a session.
|
|
84
|
+
|
|
85
|
+
To send messages via OpenClaw:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Via message tool in session
|
|
89
|
+
message tool with channel=bitchat, target=<peerID>, message="Hello!"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Architecture
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
|
|
96
|
+
│ Bitchat Peers │ │ bitchat-node │ │ OpenClaw │
|
|
97
|
+
│ (iOS, Android, │◄───►│ (BLE + HTTP) │◄───►│ Gateway │
|
|
98
|
+
│ other nodes) │ BLE │ localhost:3939 │HTTP │ + Plugin │
|
|
99
|
+
└─────────────────────┘ └──────────────────────┘ └─────────────────┘
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
1. **bitchat-node** maintains BLE connections to nearby peers
|
|
103
|
+
2. **HTTP Bridge** exposes REST API for sending/receiving messages
|
|
104
|
+
3. **OpenClaw Plugin** polls the bridge or receives webhooks for incoming messages
|
|
105
|
+
4. **Outbound messages** go through the bridge API to the BLE mesh
|
|
106
|
+
|
|
107
|
+
## API Endpoints (bitchat-node)
|
|
108
|
+
|
|
109
|
+
The plugin communicates with bitchat-node via these endpoints:
|
|
110
|
+
|
|
111
|
+
| Endpoint | Method | Description |
|
|
112
|
+
|----------|--------|-------------|
|
|
113
|
+
| `/api/status` | GET | Get node status (peerID, nickname, peer count) |
|
|
114
|
+
| `/api/peers` | GET | List connected peers |
|
|
115
|
+
| `/api/messages` | GET | Get recent messages (polling, `?since=timestamp`) |
|
|
116
|
+
| `/api/send` | POST | Send a message (`{ type, text, recipientPeerID? }`) |
|
|
117
|
+
| `/api/webhook` | POST | Register a webhook URL |
|
|
118
|
+
|
|
119
|
+
## Limitations
|
|
120
|
+
|
|
121
|
+
- **BLE Range**: Messages only propagate within BLE range (~10-100m)
|
|
122
|
+
- **No Media**: BLE bandwidth is limited; text-only messaging
|
|
123
|
+
- **No Persistence**: Messages are ephemeral; no server-side storage
|
|
124
|
+
- **Peer Discovery**: Requires active BLE scanning/advertising
|
|
125
|
+
|
|
126
|
+
## Security Considerations
|
|
127
|
+
|
|
128
|
+
- **Local Only**: All communication is local mesh; nothing leaves your network
|
|
129
|
+
- **No Encryption for Public**: Public messages are unencrypted
|
|
130
|
+
- **DM Encryption**: Direct messages use Noise protocol encryption
|
|
131
|
+
- **DM Policy**: Configure `dmPolicy` and `allowFrom` to restrict who can message
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Clone and install
|
|
137
|
+
git clone https://github.com/wkyleg/openclaw-bitchat.git
|
|
138
|
+
cd openclaw-bitchat
|
|
139
|
+
npm install
|
|
140
|
+
|
|
141
|
+
# Build
|
|
142
|
+
npm run build
|
|
143
|
+
|
|
144
|
+
# Link for development
|
|
145
|
+
openclaw plugins install -l .
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Related
|
|
149
|
+
|
|
150
|
+
- [bitchat-node](https://github.com/wkyleg/bitchat-node) — Node.js Bitchat implementation
|
|
151
|
+
- [Bitchat iOS](https://github.com/...) — Original iOS implementation
|
|
152
|
+
- [OpenClaw Plugins](https://docs.openclaw.ai/plugins) — Plugin development guide
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
[Unlicense](https://unlicense.org/) — Public Domain
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitchat Bridge
|
|
3
|
+
*
|
|
4
|
+
* Handles communication with the bitchat-node HTTP bridge.
|
|
5
|
+
* Supports both polling and WebSocket connections for real-time messages.
|
|
6
|
+
*/
|
|
7
|
+
import type { BitchatInboundMessage } from './index.js';
|
|
8
|
+
import type { Logger } from './types.js';
|
|
9
|
+
export interface BridgeOptions {
|
|
10
|
+
bridgeUrl: string;
|
|
11
|
+
nickname: string;
|
|
12
|
+
onMessage: (msg: BitchatInboundMessage) => Promise<void>;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
pollIntervalMs?: number;
|
|
15
|
+
}
|
|
16
|
+
interface BridgeStatus {
|
|
17
|
+
connected: boolean;
|
|
18
|
+
peerID?: string;
|
|
19
|
+
nickname?: string;
|
|
20
|
+
peersCount?: number;
|
|
21
|
+
}
|
|
22
|
+
interface PendingMessage {
|
|
23
|
+
id: string;
|
|
24
|
+
type: 'public' | 'direct';
|
|
25
|
+
senderPeerID: string;
|
|
26
|
+
senderNickname: string;
|
|
27
|
+
text: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
export declare class BitchatBridge {
|
|
31
|
+
private readonly bridgeUrl;
|
|
32
|
+
private readonly onMessage;
|
|
33
|
+
private readonly logger;
|
|
34
|
+
private readonly pollIntervalMs;
|
|
35
|
+
private connected;
|
|
36
|
+
private pollTimer;
|
|
37
|
+
private lastMessageTimestamp;
|
|
38
|
+
private ws;
|
|
39
|
+
constructor(options: BridgeOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Connect to the bitchat-node bridge
|
|
42
|
+
*/
|
|
43
|
+
connect(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Disconnect from the bridge
|
|
46
|
+
*/
|
|
47
|
+
disconnect(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Check if connected
|
|
50
|
+
*/
|
|
51
|
+
isConnected(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Get bridge status
|
|
54
|
+
*/
|
|
55
|
+
getStatus(): Promise<BridgeStatus>;
|
|
56
|
+
/**
|
|
57
|
+
* Send a public message to all peers
|
|
58
|
+
*/
|
|
59
|
+
sendPublicMessage(text: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Send a direct message to a specific peer
|
|
62
|
+
*/
|
|
63
|
+
sendDirectMessage(recipientPeerID: string, text: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Get list of known peers
|
|
66
|
+
*/
|
|
67
|
+
getPeers(): Promise<{
|
|
68
|
+
peerID: string;
|
|
69
|
+
nickname: string;
|
|
70
|
+
lastSeen: number;
|
|
71
|
+
}[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Get recent messages (for polling)
|
|
74
|
+
*/
|
|
75
|
+
getMessages(since?: number): Promise<PendingMessage[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Register webhook for incoming messages
|
|
78
|
+
*/
|
|
79
|
+
registerWebhook(webhookUrl: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Connect via WebSocket for real-time messages
|
|
82
|
+
*/
|
|
83
|
+
private connectWebSocket;
|
|
84
|
+
/**
|
|
85
|
+
* Start polling for messages
|
|
86
|
+
*/
|
|
87
|
+
private startPolling;
|
|
88
|
+
}
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,YAAY;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgD;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,EAAE,CAA0B;gBAExB,OAAO,EAAE,aAAa;IAOlC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB9B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAcjC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAQxC;;OAEG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAapD;;OAEG;IACG,iBAAiB,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7E;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAQnF;;OAEG;IACG,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAY5D;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxD;;OAEG;YACW,gBAAgB;IAwD9B;;OAEG;IACH,OAAO,CAAC,YAAY;CAmCrB"}
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitchat Bridge
|
|
3
|
+
*
|
|
4
|
+
* Handles communication with the bitchat-node HTTP bridge.
|
|
5
|
+
* Supports both polling and WebSocket connections for real-time messages.
|
|
6
|
+
*/
|
|
7
|
+
export class BitchatBridge {
|
|
8
|
+
bridgeUrl;
|
|
9
|
+
onMessage;
|
|
10
|
+
logger;
|
|
11
|
+
pollIntervalMs;
|
|
12
|
+
connected = false;
|
|
13
|
+
pollTimer = null;
|
|
14
|
+
lastMessageTimestamp = 0;
|
|
15
|
+
ws = null;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.bridgeUrl = options.bridgeUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
18
|
+
this.onMessage = options.onMessage;
|
|
19
|
+
this.logger = options.logger;
|
|
20
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Connect to the bitchat-node bridge
|
|
24
|
+
*/
|
|
25
|
+
async connect() {
|
|
26
|
+
// First, verify the bridge is reachable
|
|
27
|
+
try {
|
|
28
|
+
const status = await this.getStatus();
|
|
29
|
+
this.logger.info(`[bitchat-bridge] Connected to bridge. PeerID: ${status.peerID}`);
|
|
30
|
+
this.connected = true;
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
this.logger.error('[bitchat-bridge] Failed to connect to bridge:', err);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
// Try WebSocket first, fall back to polling
|
|
37
|
+
const wsUrl = this.bridgeUrl.replace(/^http/, 'ws') + '/ws';
|
|
38
|
+
try {
|
|
39
|
+
await this.connectWebSocket(wsUrl);
|
|
40
|
+
this.logger.info('[bitchat-bridge] WebSocket connected');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
this.logger.info('[bitchat-bridge] WebSocket unavailable, using polling');
|
|
44
|
+
this.startPolling();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Disconnect from the bridge
|
|
49
|
+
*/
|
|
50
|
+
async disconnect() {
|
|
51
|
+
this.connected = false;
|
|
52
|
+
if (this.pollTimer) {
|
|
53
|
+
clearInterval(this.pollTimer);
|
|
54
|
+
this.pollTimer = null;
|
|
55
|
+
}
|
|
56
|
+
if (this.ws) {
|
|
57
|
+
this.ws.close();
|
|
58
|
+
this.ws = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if connected
|
|
63
|
+
*/
|
|
64
|
+
isConnected() {
|
|
65
|
+
return this.connected;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get bridge status
|
|
69
|
+
*/
|
|
70
|
+
async getStatus() {
|
|
71
|
+
const response = await fetch(`${this.bridgeUrl}/api/status`);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`Bridge status failed: ${response.status}`);
|
|
74
|
+
}
|
|
75
|
+
return response.json();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Send a public message to all peers
|
|
79
|
+
*/
|
|
80
|
+
async sendPublicMessage(text) {
|
|
81
|
+
const response = await fetch(`${this.bridgeUrl}/api/send`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({ type: 'public', text }),
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const error = await response.text();
|
|
88
|
+
throw new Error(`Failed to send public message: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Send a direct message to a specific peer
|
|
93
|
+
*/
|
|
94
|
+
async sendDirectMessage(recipientPeerID, text) {
|
|
95
|
+
const response = await fetch(`${this.bridgeUrl}/api/send`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
body: JSON.stringify({ type: 'direct', recipientPeerID, text }),
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const error = await response.text();
|
|
102
|
+
throw new Error(`Failed to send direct message: ${error}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get list of known peers
|
|
107
|
+
*/
|
|
108
|
+
async getPeers() {
|
|
109
|
+
const response = await fetch(`${this.bridgeUrl}/api/peers`);
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`Failed to get peers: ${response.status}`);
|
|
112
|
+
}
|
|
113
|
+
return response.json();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get recent messages (for polling)
|
|
117
|
+
*/
|
|
118
|
+
async getMessages(since) {
|
|
119
|
+
const url = since
|
|
120
|
+
? `${this.bridgeUrl}/api/messages?since=${since}`
|
|
121
|
+
: `${this.bridgeUrl}/api/messages`;
|
|
122
|
+
const response = await fetch(url);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new Error(`Failed to get messages: ${response.status}`);
|
|
125
|
+
}
|
|
126
|
+
return response.json();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Register webhook for incoming messages
|
|
130
|
+
*/
|
|
131
|
+
async registerWebhook(webhookUrl) {
|
|
132
|
+
const response = await fetch(`${this.bridgeUrl}/api/webhook`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
body: JSON.stringify({ url: webhookUrl }),
|
|
136
|
+
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const error = await response.text();
|
|
139
|
+
throw new Error(`Failed to register webhook: ${error}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Connect via WebSocket for real-time messages
|
|
144
|
+
*/
|
|
145
|
+
async connectWebSocket(wsUrl) {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
try {
|
|
148
|
+
// Note: In Node.js, we'd use the 'ws' package
|
|
149
|
+
// This is a simplified version that may need adjustment
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
151
|
+
const WebSocketImpl = typeof WebSocket !== 'undefined' ? WebSocket : require('ws');
|
|
152
|
+
const ws = new WebSocketImpl(wsUrl);
|
|
153
|
+
this.ws = ws;
|
|
154
|
+
const timeout = setTimeout(() => {
|
|
155
|
+
reject(new Error('WebSocket connection timeout'));
|
|
156
|
+
}, 5000);
|
|
157
|
+
ws.onopen = () => {
|
|
158
|
+
clearTimeout(timeout);
|
|
159
|
+
resolve();
|
|
160
|
+
};
|
|
161
|
+
ws.onerror = (err) => {
|
|
162
|
+
clearTimeout(timeout);
|
|
163
|
+
reject(err);
|
|
164
|
+
};
|
|
165
|
+
ws.onmessage = async (event) => {
|
|
166
|
+
try {
|
|
167
|
+
const data = JSON.parse(String(event.data));
|
|
168
|
+
if (data.type === 'message') {
|
|
169
|
+
await this.onMessage({
|
|
170
|
+
type: data.isDirect ? 'direct' : 'public',
|
|
171
|
+
senderPeerID: data.senderPeerID,
|
|
172
|
+
senderNickname: data.senderNickname,
|
|
173
|
+
text: data.text,
|
|
174
|
+
timestamp: data.timestamp,
|
|
175
|
+
messageId: data.id ?? `ws-${Date.now()}`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
this.logger.error('[bitchat-bridge] WebSocket message parse error:', err);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
ws.onclose = () => {
|
|
184
|
+
this.logger.info('[bitchat-bridge] WebSocket closed');
|
|
185
|
+
this.ws = null;
|
|
186
|
+
// Reconnect or fall back to polling
|
|
187
|
+
if (this.connected) {
|
|
188
|
+
this.startPolling();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
reject(err);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Start polling for messages
|
|
199
|
+
*/
|
|
200
|
+
startPolling() {
|
|
201
|
+
if (this.pollTimer) {
|
|
202
|
+
return; // Already polling
|
|
203
|
+
}
|
|
204
|
+
this.logger.info(`[bitchat-bridge] Starting message polling (${this.pollIntervalMs}ms)`);
|
|
205
|
+
this.pollTimer = setInterval(async () => {
|
|
206
|
+
if (!this.connected) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const messages = await this.getMessages(this.lastMessageTimestamp);
|
|
211
|
+
for (const msg of messages) {
|
|
212
|
+
// Update timestamp to avoid duplicates
|
|
213
|
+
if (msg.timestamp > this.lastMessageTimestamp) {
|
|
214
|
+
this.lastMessageTimestamp = msg.timestamp;
|
|
215
|
+
}
|
|
216
|
+
await this.onMessage({
|
|
217
|
+
type: msg.type,
|
|
218
|
+
senderPeerID: msg.senderPeerID,
|
|
219
|
+
senderNickname: msg.senderNickname,
|
|
220
|
+
text: msg.text,
|
|
221
|
+
timestamp: msg.timestamp,
|
|
222
|
+
messageId: msg.id,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
this.logger.error('[bitchat-bridge] Poll error:', err);
|
|
228
|
+
}
|
|
229
|
+
}, this.pollIntervalMs);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openclaw/bitchat - Bitchat BLE Mesh Channel Plugin
|
|
3
|
+
*
|
|
4
|
+
* This plugin enables OpenClaw to communicate via the Bitchat
|
|
5
|
+
* peer-to-peer BLE mesh network.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - bitchat-node runs as a background service (separate process)
|
|
9
|
+
* - This plugin communicates with it via HTTP bridge
|
|
10
|
+
* - Inbound: webhook from bitchat-node → OpenClaw session
|
|
11
|
+
* - Outbound: OpenClaw → HTTP API → bitchat-node → BLE mesh
|
|
12
|
+
*/
|
|
13
|
+
import type { PluginApi, ChannelPlugin } from './types.js';
|
|
14
|
+
export interface BitchatConfig {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
nickname?: string;
|
|
17
|
+
bridgeUrl?: string;
|
|
18
|
+
webhookPath?: string;
|
|
19
|
+
autoStart?: boolean;
|
|
20
|
+
dmPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
21
|
+
allowFrom?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface BitchatInboundMessage {
|
|
24
|
+
type: 'public' | 'direct';
|
|
25
|
+
senderPeerID: string;
|
|
26
|
+
senderNickname: string;
|
|
27
|
+
text: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
messageId: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Main plugin registration
|
|
33
|
+
*/
|
|
34
|
+
export default function register(api: PluginApi): void;
|
|
35
|
+
export type { PluginApi, ChannelPlugin };
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI3D,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAGD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAiID;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAqHrD;AAGD,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC"}
|