mixin_bot 2.6.0 → 3.0.2

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +3 -3
  3. data/API_COVERAGE.md +1 -0
  4. data/CHANGELOG.md +22 -1
  5. data/README.md +116 -5
  6. data/docs/agent/cookbook.md +49 -0
  7. data/examples/blaze_async.rb +6 -4
  8. data/lib/generators/mixin_bot/authentication/USAGE +29 -0
  9. data/lib/generators/mixin_bot/authentication/authentication_generator.rb +82 -0
  10. data/lib/generators/mixin_bot/authentication/templates/app/controllers/concerns/authentication.rb.tt +52 -0
  11. data/lib/generators/mixin_bot/authentication/templates/app/controllers/sessions_controller.rb.tt +24 -0
  12. data/lib/generators/mixin_bot/authentication/templates/app/models/current.rb.tt +4 -0
  13. data/lib/generators/mixin_bot/authentication/templates/app/models/session.rb.tt +3 -0
  14. data/lib/generators/mixin_bot/authentication/templates/app/models/user.rb.tt +12 -0
  15. data/lib/generators/mixin_bot/authentication/templates/app/views/sessions/new.html.erb.tt +3 -0
  16. data/lib/generators/mixin_bot/authentication/templates/config/initializers/omniauth.rb.tt +21 -0
  17. data/lib/generators/mixin_bot/blaze/USAGE +19 -0
  18. data/lib/generators/mixin_bot/blaze/blaze_generator.rb +43 -0
  19. data/lib/generators/mixin_bot/blaze/templates/app/mixin/handlers/text_handler.rb +14 -0
  20. data/lib/generators/mixin_bot/blaze/templates/config/initializers/mixin_bot_blaze.rb +23 -0
  21. data/lib/generators/mixin_bot/install/USAGE +24 -0
  22. data/lib/generators/mixin_bot/install/install_generator.rb +50 -0
  23. data/lib/generators/mixin_bot/notifications/USAGE +13 -0
  24. data/lib/generators/mixin_bot/notifications/notifications_generator.rb +49 -0
  25. data/lib/generators/mixin_bot/notifications/templates/app/notifications/payment_received_notification.rb +24 -0
  26. data/lib/generators/mixin_bot/outputs/USAGE +29 -0
  27. data/lib/generators/mixin_bot/outputs/outputs_generator.rb +50 -0
  28. data/lib/generators/mixin_bot/outputs/templates/app/mixin/processors/deposit_processor.rb +21 -0
  29. data/lib/generators/mixin_bot/outputs/templates/app/models/mixin_output.rb +8 -0
  30. data/lib/generators/mixin_bot/outputs/templates/db/migrate/create_mixin_outputs.rb.tt +26 -0
  31. data/lib/generators/mixin_bot/transfers/USAGE +37 -0
  32. data/lib/generators/mixin_bot/transfers/templates/app/jobs/mixin_transfers/perform_job.rb +13 -0
  33. data/lib/generators/mixin_bot/transfers/templates/app/jobs/mixin_transfers/reconcile_job.rb +14 -0
  34. data/lib/generators/mixin_bot/transfers/templates/app/models/mixin_transfer.rb +11 -0
  35. data/lib/generators/mixin_bot/transfers/templates/db/migrate/create_mixin_transfers.rb.tt +23 -0
  36. data/lib/generators/mixin_bot/transfers/transfers_generator.rb +67 -0
  37. data/lib/generators/mixin_bot.rb +10 -0
  38. data/lib/mixin_bot/api/app.rb +20 -0
  39. data/lib/mixin_bot/api/blaze_async.rb +5 -1
  40. data/lib/mixin_bot/api/snapshot.rb +5 -0
  41. data/lib/mixin_bot/blaze/reactor.rb +249 -0
  42. data/lib/mixin_bot/blaze/router.rb +333 -0
  43. data/lib/mixin_bot/cache_store_adapter.rb +3 -1
  44. data/lib/mixin_bot/client.rb +2 -1
  45. data/lib/mixin_bot/computer.rb +3 -1
  46. data/lib/mixin_bot/configuration.rb +52 -0
  47. data/lib/mixin_bot/json_decoder.rb +14 -0
  48. data/lib/mixin_bot/notifications/mixin_channel.rb +80 -0
  49. data/lib/mixin_bot/outputs/cursor_model.rb +33 -0
  50. data/lib/mixin_bot/outputs/envelope.rb +160 -0
  51. data/lib/mixin_bot/outputs/memory_stores.rb +97 -0
  52. data/lib/mixin_bot/outputs/poller.rb +178 -0
  53. data/lib/mixin_bot/outputs/processing_job.rb +54 -0
  54. data/lib/mixin_bot/outputs/processors.rb +124 -0
  55. data/lib/mixin_bot/outputs/receipt_model.rb +86 -0
  56. data/lib/mixin_bot/outputs.rb +42 -0
  57. data/lib/mixin_bot/rails/railtie.rb +44 -0
  58. data/lib/mixin_bot/rails/tasks/poller.rake +33 -0
  59. data/lib/mixin_bot/rails.rb +34 -0
  60. data/lib/mixin_bot/registry.rb +79 -0
  61. data/lib/mixin_bot/transfers/model.rb +112 -0
  62. data/lib/mixin_bot/transfers/performer.rb +64 -0
  63. data/lib/mixin_bot/transfers/reconcile.rb +53 -0
  64. data/lib/mixin_bot/transfers.rb +23 -0
  65. data/lib/mixin_bot/version.rb +1 -1
  66. data/lib/mixin_bot.rb +4 -0
  67. data/lib/puma/plugin/mixin_blaze.rb +263 -0
  68. data/llms.txt +1 -1
  69. metadata +69 -7
@@ -0,0 +1,24 @@
1
+ Description:
2
+ Sets up a Mixin Messenger/Network Rails application in one command:
3
+ "Sign in with Mixin" (OmniAuth), Blaze message routing with the Puma
4
+ plugin, the outputs poller with an example processor, the outbound
5
+ transfer ledger, and Noticed-driven Mixin notifications.
6
+
7
+ Skip any component:
8
+
9
+ rails generate mixin_bot:install --skip-notifications --skip-transfers
10
+ rails generate mixin_bot:install --skip notifications,transfers
11
+ rails generate mixin_bot:install --skip authentication
12
+
13
+ Example:
14
+ rails generate mixin_bot:install
15
+
16
+ This will run, in order:
17
+ rails generate mixin_bot:authentication
18
+ rails generate mixin_bot:blaze
19
+ rails generate mixin_bot:outputs
20
+ rails generate mixin_bot:transfers
21
+ rails generate mixin_bot:notifications
22
+
23
+ Afterwards: set your bot credentials, run `rails db:migrate`, and start
24
+ the poller (`rails mixin_bot:poller`) and your job backend.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module MixinBot
6
+ module Generators
7
+ # `rails generate mixin_bot:install` — one-command setup: runs every
8
+ # component generator in dependency order. Skip any of them:
9
+ #
10
+ # rails g mixin_bot:install --skip-notifications --skip-transfers
11
+ # rails g mixin_bot:install --skip notifications transfers
12
+ # rails g mixin_bot:install --skip=notifications,transfers
13
+ #
14
+ # Idempotent: each component generator (and this one) is safe to re-run.
15
+ class InstallGenerator < Rails::Generators::Base
16
+ COMPONENTS = %w[authentication blaze outputs transfers notifications].freeze
17
+
18
+ class_option :skip,
19
+ type: :array, default: [],
20
+ banner: 'component,component2',
21
+ desc: "Skip components (comma-separated; any of: #{COMPONENTS.join(' ')})"
22
+
23
+ COMPONENTS.each do |component|
24
+ class_option :"skip_#{component}", type: :boolean, default: false,
25
+ desc: "Skip the #{component} component"
26
+
27
+ define_method(:"install_#{component}") do
28
+ invoke "mixin_bot:#{component}" unless skip?(component)
29
+ end
30
+ end
31
+
32
+ def show_plan
33
+ skipped = COMPONENTS.select { |component| skip?(component) }
34
+ say_status :install, if skipped.empty?
35
+ 'all components'
36
+ else
37
+ "all components except #{skipped.join(', ')}"
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def skip?(component)
44
+ return true if options[:"skip_#{component}"]
45
+
46
+ Array(options[:skip]).flat_map { |value| value.to_s.split(',') }.include?(component.to_s)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Adds Noticed-driven Mixin message notifications: ensures the `noticed`
3
+ gem is in the Gemfile and scaffolds one example notification delivered
4
+ through MixinBot's Mixin channel (`deliver_by :mixin`) over the HTTP
5
+ message API.
6
+
7
+ Example:
8
+ rails generate mixin_bot:notifications
9
+
10
+ This will create:
11
+ app/notifications/payment_received_notification.rb
12
+
13
+ It will also ensure `noticed` is present in the Gemfile.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module MixinBot
6
+ module Generators
7
+ # `rails generate mixin_bot:notifications` — adds the Noticed gem and
8
+ # scaffolds one example notification delivered as a Mixin message through
9
+ # the gem's MixinChannel (`deliver_by :mixin`).
10
+ class NotificationsGenerator < Rails::Generators::Base
11
+ # Rails 8.1 moved bundle_command out of Actions into BundleHelper.
12
+ unless method_defined?(:bundle_command) || private_method_defined?(:bundle_command)
13
+ begin
14
+ require 'rails/generators/bundle_helper'
15
+ include Rails::Generators::BundleHelper
16
+ rescue LoadError
17
+ # Older Rails ships bundle_command in Rails::Generators::Actions.
18
+ end
19
+ end
20
+
21
+ source_root File.expand_path('templates', __dir__)
22
+
23
+ NOTICED_GEM = 'noticed'
24
+
25
+ def add_noticed_gem
26
+ gemfile = File.read(gemfile_path)
27
+ pattern = /gem\s+["']#{NOTICED_GEM}["']/
28
+ return if gemfile.match?(/^\s*#{pattern}/)
29
+
30
+ if gemfile.match?(/^\s*#\s*#{pattern}/)
31
+ uncomment_lines 'Gemfile', pattern
32
+ bundle_command('install --quiet')
33
+ else
34
+ bundle_command("add #{NOTICED_GEM} --quiet")
35
+ end
36
+ end
37
+
38
+ def create_example_notification
39
+ template 'app/notifications/payment_received_notification.rb'
40
+ end
41
+
42
+ private
43
+
44
+ def gemfile_path
45
+ File.expand_path('Gemfile', destination_root)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example notification delivered as a Mixin message through the mixin channel:
4
+ #
5
+ # PaymentReceivedNotification.with(amount: '1.5').deliver(user)
6
+ #
7
+ # (`user` must answer #mixin_user_id — the mixin_bot:authentication
8
+ # generator's users table has that column.)
9
+ #
10
+ # The recipient and message contract lives here; MixinBot's channel sends
11
+ # over the HTTP message API with the configured bot's credentials.
12
+ class PaymentReceivedNotification < Noticed::Notification
13
+ deliver_by :mixin
14
+
15
+ param :amount
16
+
17
+ def mixin_recipient
18
+ recipient.mixin_user_id
19
+ end
20
+
21
+ def mixin_message
22
+ { category: 'PLAIN_TEXT', content: "Payment received: #{params[:amount]}" }
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ Description:
2
+ Scaffolds the Mixin output-polling foundation: the receipt table and
3
+ model, and an example processor under app/mixin/processors. Receipts
4
+ deduplicate every observed output (inbound or outbound); the poller's
5
+ resume cursor is derived from the receipts themselves; processors are
6
+ selected by predicate and run in the job queue.
7
+
8
+ Start polling with the rake task (bots default to MixinBot's global
9
+ credentials; pass a registered bot name for multi-bot apps):
10
+
11
+ rails mixin_bot:poller # default bot
12
+ rails mixin_bot:poller[shop] # registered bot :shop
13
+
14
+ Optional filters via environment variables:
15
+
16
+ MIXIN_BOT_POLL_ASSET restrict to one asset id
17
+ MIXIN_BOT_POLL_MEMBERS comma-separated member user ids
18
+ MIXIN_BOT_POLL_THRESHOLD members threshold
19
+
20
+ Example:
21
+ rails generate mixin_bot:outputs
22
+
23
+ This will create:
24
+ db/migrate/xxx_create_mixin_outputs.rb
25
+ app/models/mixin_output.rb
26
+ app/mixin/processors/deposit_processor.rb
27
+
28
+ Run `rails db:migrate` afterwards, and make sure a job backend
29
+ (e.g. Solid Queue) is running for processor jobs.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module MixinBot
6
+ module Generators
7
+ # `rails generate mixin_bot:outputs` — scaffolds the output-polling
8
+ # foundation: the receipt table and model, and an example processor under
9
+ # app/mixin/processors. Pair with the `mixin_bot:poller` rake task and a
10
+ # job backend (e.g. Solid Queue). The poller's resume cursor is derived
11
+ # from the receipts themselves — there is no separate cursor table.
12
+ class OutputsGenerator < Rails::Generators::Base
13
+ include Rails::Generators::Migration
14
+
15
+ source_root File.expand_path('templates', __dir__)
16
+
17
+ def self.next_migration_number(dirname)
18
+ current = current_migration_number(dirname)
19
+ return Time.now.utc.strftime('%Y%m%d%H%M%S%6N') if current.zero?
20
+
21
+ current.succ.to_s
22
+ end
23
+
24
+ def create_migrations
25
+ migration 'create_mixin_outputs'
26
+ end
27
+
28
+ def create_model
29
+ template 'app/models/mixin_output.rb'
30
+ end
31
+
32
+ def create_example_processor
33
+ template 'app/mixin/processors/deposit_processor.rb'
34
+ end
35
+
36
+ private
37
+
38
+ # Skips the migration when a same-named one already exists (re-run).
39
+ def migration(name)
40
+ return if self.class.migration_exists?('db/migrate', name)
41
+
42
+ migration_template "db/migrate/#{name}.rb.tt", "db/migrate/#{name}.rb"
43
+ end
44
+
45
+ def migration_version
46
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixin
4
+ module Processors
5
+ # Example processor: matches every inbound (unspent) output. Replace the
6
+ # predicate and body with real business rules — processors run in the job
7
+ # queue and enqueue is at-least-once, so key side effects on
8
+ # envelope.output_id to stay idempotent.
9
+ class DepositProcessor < MixinBot::Outputs::Processor
10
+ def self.matches?(envelope)
11
+ !envelope.spent?
12
+ end
13
+
14
+ def process
15
+ Rails.logger.info(
16
+ "received #{envelope.amount} of #{envelope.asset_id} (output #{envelope.output_id})"
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Receipt row for every output the poller observes (inbound or outbound).
4
+ # The (bot_app_id, output_id) unique index is the dedup gate; snapshot-derived
5
+ # fields (memo, opponent_id, trace_id) are cached here by the envelope bridge.
6
+ class MixinOutput < ApplicationRecord
7
+ include MixinBot::Outputs::ReceiptModel
8
+ end
@@ -0,0 +1,26 @@
1
+ class CreateMixinOutputs < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :mixin_outputs do |t|
4
+ t.string :bot_app_id, null: false
5
+ t.string :output_id, null: false
6
+ t.string :amount
7
+ t.string :asset_id
8
+ t.string :state
9
+ t.string :transaction_hash
10
+ t.integer :output_index
11
+ t.bigint :sequence
12
+ t.datetime :output_created_at
13
+ t.text :memo
14
+ t.string :opponent_id
15
+ t.string :trace_id
16
+ t.boolean :snapshot_bridged, null: false, default: false
17
+ t.datetime :enqueued_at
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :mixin_outputs, %i[bot_app_id output_id], unique: true
22
+ add_index :mixin_outputs, :bot_app_id
23
+ # cursor lookups: MAX(sequence) per bot
24
+ add_index :mixin_outputs, %i[bot_app_id sequence]
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ Description:
2
+ Scaffolds the outbound transfer ledger: a migration and model for
3
+ MixinTransfer (with the runtime state machine), perform and reconcile
4
+ jobs under app/jobs/mixin_transfers, and a Solid Queue recurring
5
+ schedule for reconciliation.
6
+
7
+ Usage:
8
+
9
+ transfer = MixinTransfer.create!(
10
+ recipient_id: user_id, # Mixin user id of the recipient
11
+ asset_id: ASSET_ID,
12
+ amount: '1.5',
13
+ memo: 'payout',
14
+ trace_id: SecureRandom.uuid # idempotency: re-sends are deduped
15
+ )
16
+
17
+ The record is enqueued on create and ends `confirmed` (with its
18
+ transaction hash), `failed` (definitive rejection), or `reconciling`
19
+ (timeout — resolved by the recurring job).
20
+
21
+ Example:
22
+ rails generate mixin_bot:transfers
23
+
24
+ This will create:
25
+ db/migrate/xxx_create_mixin_transfers.rb
26
+ app/models/mixin_transfer.rb
27
+ app/jobs/mixin_transfers/perform_job.rb
28
+ app/jobs/mixin_transfers/reconcile_job.rb
29
+ config/recurring.yml
30
+
31
+ Run `rails db:migrate` afterwards. If config/recurring.yml already
32
+ exists, add the reconcile entry manually:
33
+
34
+ production:
35
+ mixin_transfers_reconcile:
36
+ class: MixinTransfers.ReconcileJob
37
+ schedule: every 5 minutes
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinTransfers
4
+ # Executes the Safe pipeline for one MixinTransfer. Retries are safe: the
5
+ # transfer's trace id deduplicates network-side.
6
+ class PerformJob < ApplicationJob
7
+ queue_as :mixin_transfers
8
+
9
+ def perform(transfer_id)
10
+ MixinTransfer.find(transfer_id).perform!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinTransfers
4
+ # Resolves transfers whose send attempt ended indeterminately (timeout
5
+ # after submission): confirms from the trace snapshot or re-sends with the
6
+ # same trace id. Wired by the recurring schedule in config/recurring.yml.
7
+ class ReconcileJob < ApplicationJob
8
+ queue_as :mixin_transfers
9
+
10
+ def perform
11
+ MixinTransfer.reconcile_pending!
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Outbound transfer ledger. Creating a record enqueues the perform job; the
4
+ # runtime concern (MixinBot::Transfers::Model) drives the Safe pipeline and
5
+ # records the outcome in #state. bot_app_id selects a registered bot
6
+ # (MixinBot.register_bot); nil uses the default bot.
7
+ class MixinTransfer < ApplicationRecord
8
+ include MixinBot::Transfers::Model
9
+
10
+ after_create_commit -> { MixinTransfers::PerformJob.perform_later(id) }
11
+ end
@@ -0,0 +1,23 @@
1
+ class CreateMixinTransfers < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :mixin_transfers do |t|
4
+ t.string :recipient_id, null: false
5
+ t.string :asset_id, null: false
6
+ t.decimal :amount, precision: 32, scale: 18, null: false
7
+ t.text :memo
8
+ t.string :trace_id, null: false
9
+ t.string :state, null: false, default: 'pending'
10
+ t.string :previous_state
11
+ t.string :transaction_hash
12
+ t.text :error
13
+ t.datetime :transitioned_at
14
+ t.string :bot_app_id
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ add_index :mixin_transfers, :trace_id, unique: true
20
+ add_index :mixin_transfers, :state
21
+ add_index :mixin_transfers, :bot_app_id
22
+ end
23
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module MixinBot
6
+ module Generators
7
+ # `rails generate mixin_bot:transfers` — scaffolds the outbound transfer
8
+ # ledger: migration + model (state machine concern), perform/reconcile
9
+ # jobs, and a Solid Queue recurring entry for reconciliation.
10
+ class TransfersGenerator < Rails::Generators::Base
11
+ include Rails::Generators::Migration
12
+
13
+ source_root File.expand_path('templates', __dir__)
14
+
15
+ RECURRING_ENTRY = <<~YAML
16
+ production:
17
+ mixin_transfers_reconcile:
18
+ class: MixinTransfers::ReconcileJob
19
+ schedule: every 5 minutes
20
+ YAML
21
+
22
+ def self.next_migration_number(dirname)
23
+ current = current_migration_number(dirname)
24
+ return Time.now.utc.strftime('%Y%m%d%H%M%S%6N') if current.zero?
25
+
26
+ current.succ.to_s
27
+ end
28
+
29
+ def create_ledger_migration
30
+ migration 'create_mixin_transfers'
31
+ end
32
+
33
+ def create_model
34
+ template 'app/models/mixin_transfer.rb'
35
+ end
36
+
37
+ def create_jobs
38
+ template 'app/jobs/mixin_transfers/perform_job.rb'
39
+ template 'app/jobs/mixin_transfers/reconcile_job.rb'
40
+ end
41
+
42
+ def create_recurring_schedule
43
+ path = File.expand_path('config/recurring.yml', destination_root)
44
+ if File.exist?(path)
45
+ say_status :todo, <<~MSG.chomp
46
+ add the MixinTransfers reconcile entry to config/recurring.yml (see USAGE)
47
+ MSG
48
+ else
49
+ create_file 'config/recurring.yml', RECURRING_ENTRY
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Skips the migration when a same-named one already exists (re-run).
56
+ def migration(name)
57
+ return if self.class.migration_exists?('db/migrate', name)
58
+
59
+ migration_template "db/migrate/#{name}.rb.tt", "db/migrate/#{name}.rb"
60
+ end
61
+
62
+ def migration_version
63
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ module Generators
5
+ # Generator namespace discovered by railties: every bundler-managed gem's
6
+ # `lib/generators` directory is on the generator load path, so apps can run
7
+ # `rails generate mixin_bot:<name>` without any configuration. Individual
8
+ # generators live in sibling directories (e.g. mixin_bot/authentication).
9
+ end
10
+ end
@@ -85,6 +85,26 @@ module MixinBot
85
85
  client.post path, **payload, access_token: kwargs[:access_token]
86
86
  end
87
87
 
88
+ ##
89
+ # Updates the app security profile — the allowed IP addresses and the
90
+ # resource URL patterns.
91
+ #
92
+ # +POST /apps/:id+ does not persist these two fields; the security
93
+ # endpoint is the only one that stores them. Both lists are submitted on
94
+ # every call, so fetch the live values first when only one list should
95
+ # change.
96
+ #
97
+ # @param allowed_ips [Array<String>] IPv4 / CIDR entries; +[]+ removes
98
+ # the restriction
99
+ # @param resource_patterns [Array<String>] URL patterns; +[]+ removes the
100
+ # restriction
101
+ # @return [MixinBot::Models::ApiEnvelope]
102
+ #
103
+ def update_app_security(app_id, allowed_ips: [], resource_patterns: [], access_token: nil)
104
+ path = format('/apps/%<id>s/security', id: app_id)
105
+ client.post path, allowed_ips:, resource_patterns:, access_token:
106
+ end
107
+
88
108
  def rotate_app_secret(app_id, access_token: nil)
89
109
  path = format('/apps/%<id>s/secret', id: app_id)
90
110
  client.post path, access_token:
@@ -33,7 +33,11 @@ module MixinBot
33
33
  # +handler:+ is yield-through so the caller can supply an
34
34
  # +Async::WebSocket::Connection+ subclass (e.g. one that correlates
35
35
  # PONG frames). +endpoint_options:+ are forwarded to
36
- # +Async::HTTP::Endpoint.parse+ (e.g. +timeout:+ for the connect phase).
36
+ # +Async::HTTP::Endpoint.parse+. Prefer not passing +timeout:+ there: it
37
+ # becomes the socket's per-read timeout, so a quiet-but-healthy
38
+ # connection dies when no frame arrives within that window. Bound the
39
+ # connect phase at the call site instead (see
40
+ # MixinBot::Blaze::Reactor#connect!).
37
41
  def blaze_async(handler: Async::WebSocket::Connection, endpoint_options: {})
38
42
  access_token = access_token('GET', '/', '')
39
43
  authorization = format('Bearer %<access_token>s', access_token:)
@@ -23,6 +23,11 @@ module MixinBot
23
23
  end
24
24
  alias safe_snapshot_by_id safe_snapshot
25
25
 
26
+ def safe_snapshot_by_trace(trace_id, access_token: nil)
27
+ path = format('/safe/snapshots/trace/%<trace_id>s', trace_id:)
28
+ client.get path, access_token:
29
+ end
30
+
26
31
  def create_safe_snapshot_notification(**kwargs)
27
32
  path = '/safe/snapshots/notifications'
28
33