mixin_bot 2.5.0 → 2.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +34 -1
- data/examples/blaze_async.rb +89 -0
- data/lib/mixin_bot/api/blaze_async.rb +58 -0
- data/lib/mixin_bot/api.rb +2 -0
- data/lib/mixin_bot/cli/base.rb +1 -0
- data/lib/mixin_bot/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42fc9aa30981430bf8a4b32ac343994fedb94f3a318d4c519404f788f66d8c68
|
|
4
|
+
data.tar.gz: df1037544f1eb11ec4cf196aacbd076db8a3aa6c2dc11ed1d138751cdb37e073
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a12c51222b50582cd31b4e80d17f3f95c73627845ec3500701a02de21d68de887e85ccf599e505be8ec81ce6c903485ec193ae6ee3e865dce9e2bd2b984ef023
|
|
7
|
+
data.tar.gz: 1dbb16ececb43acf6febcc42467ffb631b064fcb508f235c4a6e19148c15c328935a9ec1a297bffa45baebd70fc0b7458bf98d374fc2f5c0930734345bf67d29
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.6.0] - 2026-09-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`API#blaze_async`** — fiber-based sibling of `blaze`: returns a connected `async-websocket` client (same `wss://<blaze_host>/` URL, `Mixin-Blaze-1` subprotocol, Bearer JWT, User-Agent, and shared frame codec) for hosts driving Blaze outside EventMachine. `handler:` is yield-through for caller-supplied `Async::WebSocket::Connection` subclasses; `endpoint_options:` forwards to `Async::HTTP::Endpoint.parse`. Forces the HTTP/1 upgrade path via ALPN. Caller-side wire notes: send `write_ws_message` byte arrays as `BinaryMessage.new(ary.pack('C*'))`, feed `message.to_str` into `ws_message`, and own the keepalive ping (server pings are auto-replied).
|
|
15
|
+
|
|
10
16
|
## [2.5.0] - 2026-09-03
|
|
11
17
|
|
|
12
18
|
### Added
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Ruby SDK and CLI for [Mixin Network](https://developers.mixin.one/docs): authent
|
|
|
6
6
|
|
|
7
7
|
The gem aims for **parity with the official [bot-api-go-client](https://github.com/MixinNetwork/bot-api-go-client)** Go SDK and **[bot-api-nodejs-client](https://github.com/MixinNetwork/bot-api-nodejs-client)** Node SDK. See [API_COVERAGE.md](API_COVERAGE.md) for the full mapping; run `rake mixin_bot:api_coverage` to confirm no gaps are marked missing.
|
|
8
8
|
|
|
9
|
-
Current gem version: **2.
|
|
9
|
+
Current gem version: **2.6.0** (see [CHANGELOG.md](CHANGELOG.md) for breaking changes and deprecations).
|
|
10
10
|
|
|
11
11
|
## Requirements
|
|
12
12
|
|
|
@@ -257,6 +257,39 @@ end
|
|
|
257
257
|
|
|
258
258
|
For outbound messages over an open socket, use `blaze_send_plain_text`, `blaze_send_contact`, `blaze_send_app_card`, and related helpers (parity with Go `BlazeClient`).
|
|
259
259
|
|
|
260
|
+
### Without EventMachine: `blaze_async`
|
|
261
|
+
|
|
262
|
+
`blaze_async` returns the connected `async-websocket` connection instead of a `Faye::WebSocket::Client` — same URL, `Mixin-Blaze-1` subprotocol, and frame codec; the caller drives the loop in an `Async` reactor. Wire notes: wrap `write_ws_message` byte arrays in `Protocol::WebSocket::BinaryMessage`, feed `message.to_str` into `ws_message`, and own the keepalive ping (see `examples/blaze_async.rb`).
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
require 'async'
|
|
266
|
+
require 'mixin_bot'
|
|
267
|
+
|
|
268
|
+
Async do |task|
|
|
269
|
+
connection = MixinBot.api.blaze_async
|
|
270
|
+
|
|
271
|
+
task.async do # keepalive is the caller's job
|
|
272
|
+
loop do
|
|
273
|
+
sleep 30
|
|
274
|
+
connection.send_ping
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
connection.write Protocol::WebSocket::BinaryMessage.new(MixinBot.api.list_pending_message.pack('C*'))
|
|
279
|
+
while (message = connection.read)
|
|
280
|
+
raw = JSON.parse MixinBot.api.ws_message(message.to_str)
|
|
281
|
+
# data is a Hash for message envelopes, an Array in LIST_PENDING_MESSAGES replies
|
|
282
|
+
data = raw['data'].is_a?(Hash) ? raw['data'] : {}
|
|
283
|
+
next unless (message_id = data['message_id'])
|
|
284
|
+
|
|
285
|
+
bytes = MixinBot.api.acknowledge_message_receipt(message_id)
|
|
286
|
+
connection.write Protocol::WebSocket::BinaryMessage.new(bytes.pack('C*'))
|
|
287
|
+
end
|
|
288
|
+
ensure
|
|
289
|
+
connection&.close
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
260
293
|
## Deep links and bot auth
|
|
261
294
|
|
|
262
295
|
```ruby
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Fiber-based Blaze client — the async-websocket sibling of examples/blaze.rb.
|
|
4
|
+
#
|
|
5
|
+
# Drives API#blaze_async without EventMachine: the gem returns the connected
|
|
6
|
+
# connection and the caller runs the loop inside an Async reactor. The frame
|
|
7
|
+
# codec (write_ws_message / ws_message / list_pending_message /
|
|
8
|
+
# acknowledge_message_receipt) is shared with blaze verbatim; only the wire
|
|
9
|
+
# differs:
|
|
10
|
+
# - *Send*: write_ws_message returns an Array of byte integers (Faye framed
|
|
11
|
+
# that itself) — wrap it in a Protocol::WebSocket::BinaryMessage.
|
|
12
|
+
# - *Read*: connection.read yields a Protocol::WebSocket::Message (or nil
|
|
13
|
+
# after a clean close) — feed message.to_str into ws_message.
|
|
14
|
+
# - *Keepalive*: Faye's ping: 60 becomes the caller's job; server pings are
|
|
15
|
+
# auto-replied by the protocol layer.
|
|
16
|
+
#
|
|
17
|
+
# Run: ruby examples/blaze_async.rb
|
|
18
|
+
|
|
19
|
+
require './lib/mixin_bot'
|
|
20
|
+
require 'async'
|
|
21
|
+
require 'base64'
|
|
22
|
+
require 'json'
|
|
23
|
+
require 'securerandom'
|
|
24
|
+
require 'yaml'
|
|
25
|
+
|
|
26
|
+
CONFIG = YAML.load_file("#{File.dirname __FILE__}/config.yml")
|
|
27
|
+
MixinBot.configure do
|
|
28
|
+
self.app_id = CONFIG['app_id']
|
|
29
|
+
self.client_secret = CONFIG['client_secret']
|
|
30
|
+
self.session_id = CONFIG['session_id']
|
|
31
|
+
self.server_public_key = CONFIG['server_public_key']
|
|
32
|
+
self.session_private_key = CONFIG['session_private_key']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
API = MixinBot.api
|
|
36
|
+
|
|
37
|
+
# gzip+JSON bytes from write_ws_message -> one binary WebSocket frame
|
|
38
|
+
def send_frame(connection, bytes)
|
|
39
|
+
connection.write Protocol::WebSocket::BinaryMessage.new(bytes.pack('C*'))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Async do |task|
|
|
43
|
+
# endpoint_options: is forwarded to Async::HTTP::Endpoint.parse (a connect
|
|
44
|
+
# timeout here). handler: stays default; pass an Async::WebSocket::Connection
|
|
45
|
+
# subclass to hook the raw frame events (e.g. PONG correlation).
|
|
46
|
+
connection = API.blaze_async(endpoint_options: { timeout: 10 })
|
|
47
|
+
p [Time.now.to_s, :connected]
|
|
48
|
+
|
|
49
|
+
# liveness is the caller's policy: nothing on the wire goes stale quietly
|
|
50
|
+
keepalive = task.async do
|
|
51
|
+
loop do
|
|
52
|
+
sleep 30
|
|
53
|
+
connection.send_ping
|
|
54
|
+
end
|
|
55
|
+
rescue Protocol::WebSocket::ProtocolError, IOError # IOError covers EOFError
|
|
56
|
+
# connection is gone; the read loop below is already winding down
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# sends nothing on open — mirror blaze and request pending messages first
|
|
60
|
+
send_frame connection, API.list_pending_message
|
|
61
|
+
|
|
62
|
+
# read returns nil after a clean close; an abrupt one raises EOFError
|
|
63
|
+
while (message = connection.read)
|
|
64
|
+
raw = JSON.parse API.ws_message(message.to_str)
|
|
65
|
+
p [Time.now.to_s, :on_message, raw&.[]('action')]
|
|
66
|
+
|
|
67
|
+
# data is a Hash for message envelopes, but an Array (the pending list)
|
|
68
|
+
# in the LIST_PENDING_MESSAGES reply
|
|
69
|
+
data = raw['data'].is_a?(Hash) ? raw['data'] : {}
|
|
70
|
+
send_frame connection, API.acknowledge_message_receipt(data['message_id']) if data['message_id']
|
|
71
|
+
|
|
72
|
+
# echo PLAIN_TEXT back to its sender over the same connection
|
|
73
|
+
next unless data['category'] == 'PLAIN_TEXT'
|
|
74
|
+
|
|
75
|
+
send_frame connection, API.write_ws_message(
|
|
76
|
+
params: {
|
|
77
|
+
conversation_id: data['conversation_id'],
|
|
78
|
+
recipient_id: data['user_id'],
|
|
79
|
+
message_id: SecureRandom.uuid,
|
|
80
|
+
category: 'PLAIN_TEXT',
|
|
81
|
+
data_base64: Base64.urlsafe_encode64("echo: #{Base64.urlsafe_decode64(data['data'])}", padding: false)
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
p [Time.now.to_s, :closed]
|
|
86
|
+
ensure
|
|
87
|
+
keepalive&.stop
|
|
88
|
+
connection&.close
|
|
89
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'async/http/endpoint'
|
|
4
|
+
require 'async/http/protocol/http11'
|
|
5
|
+
require 'async/websocket/client'
|
|
6
|
+
|
|
7
|
+
module MixinBot
|
|
8
|
+
class API
|
|
9
|
+
# Fiber-based sibling of +Blaze#blaze+: returns a connected
|
|
10
|
+
# +Async::WebSocket+ client instead of a +Faye::WebSocket::Client+.
|
|
11
|
+
#
|
|
12
|
+
# Same endpoint, +Mixin-Blaze-1+ subprotocol, Bearer JWT, and User-Agent as
|
|
13
|
+
# +blaze+. The frame codec (+ws_message+, +write_ws_message+,
|
|
14
|
+
# +list_pending_message+, +acknowledge_message_receipt+) is
|
|
15
|
+
# transport-agnostic and shared verbatim.
|
|
16
|
+
#
|
|
17
|
+
# Wire differences the caller must handle:
|
|
18
|
+
# - *Send*: +write_ws_message+ returns an +Array+ of byte integers (the
|
|
19
|
+
# Faye backend framed that automatically); here the caller wraps it:
|
|
20
|
+
# +connection.write(Protocol::WebSocket::BinaryMessage.new(bytes.pack('C*')))+.
|
|
21
|
+
# - *Read*: +connection.read+ returns a +Protocol::WebSocket::Message+;
|
|
22
|
+
# pass +message.to_str+ (the binary buffer) into +ws_message+.
|
|
23
|
+
# - *Keepalive*: Faye's +ping: 60+ becomes the caller's job (server pings
|
|
24
|
+
# are still auto-replied by the protocol layer).
|
|
25
|
+
#
|
|
26
|
+
# Sends nothing on open — the caller sends +list_pending_message+ first,
|
|
27
|
+
# mirroring +blaze+. No per-IO read timeout by default: quiet-but-healthy
|
|
28
|
+
# connections are legitimate; liveness is the caller's policy.
|
|
29
|
+
module BlazeAsync
|
|
30
|
+
# Returns the block-less +Client.connect+ connection, preserving
|
|
31
|
+
# +blaze+'s "returns a client the caller drives" contract.
|
|
32
|
+
#
|
|
33
|
+
# +handler:+ is yield-through so the caller can supply an
|
|
34
|
+
# +Async::WebSocket::Connection+ subclass (e.g. one that correlates
|
|
35
|
+
# PONG frames). +endpoint_options:+ are forwarded to
|
|
36
|
+
# +Async::HTTP::Endpoint.parse+ (e.g. +timeout:+ for the connect phase).
|
|
37
|
+
def blaze_async(handler: Async::WebSocket::Connection, endpoint_options: {})
|
|
38
|
+
access_token = access_token('GET', '/', '')
|
|
39
|
+
authorization = format('Bearer %<access_token>s', access_token:)
|
|
40
|
+
|
|
41
|
+
endpoint = Async::HTTP::Endpoint.parse(
|
|
42
|
+
format('wss://%<host>s/', host: config.blaze_host),
|
|
43
|
+
# Force the HTTP/1 upgrade path: avoids the RFC 8441 (h2 CONNECT)
|
|
44
|
+
# route, which not every gateway negotiates the same way.
|
|
45
|
+
alpn_protocols: Async::HTTP::Protocol::HTTP11.names,
|
|
46
|
+
**endpoint_options
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
Async::WebSocket::Client.connect(
|
|
50
|
+
endpoint,
|
|
51
|
+
protocols: ['Mixin-Blaze-1'],
|
|
52
|
+
headers: { 'Authorization' => authorization, 'User-Agent' => "mixin_bot/#{MixinBot::VERSION}" },
|
|
53
|
+
handler: handler
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/mixin_bot/api.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative 'api/asset'
|
|
|
8
8
|
require_relative 'api/attachment'
|
|
9
9
|
require_relative 'api/auth'
|
|
10
10
|
require_relative 'api/blaze'
|
|
11
|
+
require_relative 'api/blaze_async'
|
|
11
12
|
require_relative 'api/chain'
|
|
12
13
|
require_relative 'api/code'
|
|
13
14
|
require_relative 'api/circle'
|
|
@@ -338,6 +339,7 @@ module MixinBot
|
|
|
338
339
|
include MixinBot::API::Attachment
|
|
339
340
|
include MixinBot::API::Auth
|
|
340
341
|
include MixinBot::API::Blaze
|
|
342
|
+
include MixinBot::API::BlazeAsync
|
|
341
343
|
include MixinBot::API::Chain
|
|
342
344
|
include MixinBot::API::Code
|
|
343
345
|
include MixinBot::API::Circle
|
data/lib/mixin_bot/cli/base.rb
CHANGED
data/lib/mixin_bot/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mixin_bot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- an-lee
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: async-websocket
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.30'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.30'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: awesome_print
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -271,6 +285,7 @@ files:
|
|
|
271
285
|
- docs/agent/cli.md
|
|
272
286
|
- docs/agent/cookbook.md
|
|
273
287
|
- examples/blaze.rb
|
|
288
|
+
- examples/blaze_async.rb
|
|
274
289
|
- examples/config.yml.example
|
|
275
290
|
- lib/mixin_bot.rb
|
|
276
291
|
- lib/mixin_bot/address.rb
|
|
@@ -281,6 +296,7 @@ files:
|
|
|
281
296
|
- lib/mixin_bot/api/attachment.rb
|
|
282
297
|
- lib/mixin_bot/api/auth.rb
|
|
283
298
|
- lib/mixin_bot/api/blaze.rb
|
|
299
|
+
- lib/mixin_bot/api/blaze_async.rb
|
|
284
300
|
- lib/mixin_bot/api/chain.rb
|
|
285
301
|
- lib/mixin_bot/api/circle.rb
|
|
286
302
|
- lib/mixin_bot/api/code.rb
|