bidi2pdf 0.1.5 → 0.1.7

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +61 -1
  4. data/README.md +47 -10
  5. data/docker/Dockerfile +11 -3
  6. data/docker/Dockerfile.chromedriver +4 -2
  7. data/docker/Dockerfile.slim +75 -0
  8. data/lib/bidi2pdf/bidi/browser_console_logger.rb +92 -0
  9. data/lib/bidi2pdf/bidi/browser_tab.rb +415 -39
  10. data/lib/bidi2pdf/bidi/client.rb +85 -23
  11. data/lib/bidi2pdf/bidi/command_manager.rb +46 -48
  12. data/lib/bidi2pdf/bidi/commands/base.rb +39 -1
  13. data/lib/bidi2pdf/bidi/commands/browser_remove_user_context.rb +27 -0
  14. data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +4 -0
  15. data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +5 -0
  16. data/lib/bidi2pdf/bidi/commands.rb +1 -0
  17. data/lib/bidi2pdf/bidi/event_manager.rb +1 -1
  18. data/lib/bidi2pdf/bidi/interceptor.rb +1 -1
  19. data/lib/bidi2pdf/bidi/js_logger_helper.rb +16 -0
  20. data/lib/bidi2pdf/bidi/logger_events.rb +66 -0
  21. data/lib/bidi2pdf/bidi/network_event.rb +40 -7
  22. data/lib/bidi2pdf/bidi/network_event_formatters/network_event_console_formatter.rb +110 -0
  23. data/lib/bidi2pdf/bidi/network_event_formatters/network_event_formatter_utils.rb +53 -0
  24. data/lib/bidi2pdf/bidi/network_event_formatters/network_event_html_formatter.rb +125 -0
  25. data/lib/bidi2pdf/bidi/network_event_formatters.rb +11 -0
  26. data/lib/bidi2pdf/bidi/network_events.rb +46 -17
  27. data/lib/bidi2pdf/bidi/session.rb +120 -13
  28. data/lib/bidi2pdf/bidi/user_context.rb +62 -0
  29. data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +7 -7
  30. data/lib/bidi2pdf/chromedriver_manager.rb +48 -21
  31. data/lib/bidi2pdf/cli.rb +27 -3
  32. data/lib/bidi2pdf/dsl.rb +33 -0
  33. data/lib/bidi2pdf/launcher.rb +34 -2
  34. data/lib/bidi2pdf/notifications/event.rb +52 -0
  35. data/lib/bidi2pdf/notifications/instrumenter.rb +65 -0
  36. data/lib/bidi2pdf/notifications/logging_subscriber.rb +136 -0
  37. data/lib/bidi2pdf/notifications.rb +78 -0
  38. data/lib/bidi2pdf/session_runner.rb +49 -7
  39. data/lib/bidi2pdf/verbose_logger.rb +79 -0
  40. data/lib/bidi2pdf/version.rb +1 -1
  41. data/lib/bidi2pdf.rb +99 -5
  42. data/sig/bidi2pdf/bidi/client.rbs +1 -1
  43. metadata +45 -4
  44. data/lib/bidi2pdf/utils.rb +0 -15
@@ -4,14 +4,41 @@ require_relative "browser_tab"
4
4
 
5
5
  module Bidi2pdf
6
6
  module Bidi
7
+ # Represents a user context for managing browser interactions and cookies
8
+ # using the Bidi2pdf library. This class provides methods for creating
9
+ # user contexts, setting cookies, and creating browser windows.
10
+ #
11
+ # @example Creating a user context
12
+ # user_context = Bidi2pdf::Bidi::UserContext.new(client)
13
+ #
14
+ # @example Setting a cookie
15
+ # user_context.set_cookie(
16
+ # name: "session",
17
+ # value: "abc123",
18
+ # domain: "example.com",
19
+ # source_origin: "http://example.com"
20
+ # )
21
+ #
22
+ # @example Creating a browser window
23
+ # browser_window = user_context.create_browser_window
24
+ #
25
+ # @param [Object] client The WebSocket client for communication.
7
26
  class UserContext
27
+ # @return [Object] The WebSocket client.
8
28
  attr_reader :client
9
29
 
30
+ # Initializes a new user context.
31
+ #
32
+ # @param [Object] client The WebSocket client for communication.
10
33
  def initialize(client)
11
34
  @client = client
12
35
  @context_id = nil
13
36
  end
14
37
 
38
+ # Retrieves the user context ID, creating it if it does not exist.
39
+ #
40
+ # @return [String] The user context ID.
41
+ # @raise [RuntimeError] If an error occurs while creating the user context.
15
42
  def context_id
16
43
  @context_id ||= begin
17
44
  res = client.send_cmd_and_wait(Bidi2pdf::Bidi::Commands::BrowserCreateUserContext.new) do |response|
@@ -26,6 +53,17 @@ module Bidi2pdf
26
53
  end
27
54
  end
28
55
 
56
+ # Sets a cookie in the user context.
57
+ #
58
+ # @param [String] name The name of the cookie.
59
+ # @param [String] value The value of the cookie.
60
+ # @param [String] domain The domain for the cookie.
61
+ # @param [String] source_origin The source origin for the cookie.
62
+ # @param [String] path The path for the cookie. Defaults to "/".
63
+ # @param [Boolean] secure Whether the cookie is secure. Defaults to true.
64
+ # @param [Boolean] http_only Whether the cookie is HTTP-only. Defaults to false.
65
+ # @param [String] same_site The SameSite attribute for the cookie. Defaults to "strict".
66
+ # @param [Integer] ttl The time-to-live for the cookie in seconds. Defaults to 30.
29
67
  def set_cookie(
30
68
  name:,
31
69
  value:,
@@ -55,6 +93,9 @@ module Bidi2pdf
55
93
  end
56
94
  end
57
95
 
96
+ # Creates a new browser window in the user context.
97
+ #
98
+ # @return [BrowserTab] The newly created browser tab.
58
99
  def create_browser_window
59
100
  cmd = Bidi2pdf::Bidi::Commands::CreateWindow.new(user_context_id: context_id)
60
101
 
@@ -64,6 +105,27 @@ module Bidi2pdf
64
105
  BrowserTab.new(client, browsing_context_id, context_id)
65
106
  end
66
107
  end
108
+
109
+ # Closes the user context.
110
+ #
111
+ # This method removes the user context from the browser, effectively cleaning up
112
+ # any associated resources. If the user context does not exist, the method does nothing.
113
+ #
114
+ # @return [nil]
115
+ # @raise [RuntimeError] If an error occurs while removing the user context.
116
+ def close
117
+ return unless context_id
118
+
119
+ res = client.send_cmd_and_wait(Bidi2pdf::Bidi::Commands::BrowserRemoveUserContext.new(user_context_id: context_id)) do |response|
120
+ raise "Error removing user context: #{response.inspect}" if response["error"]
121
+
122
+ response["result"]
123
+ end
124
+
125
+ Bidi2pdf.logger.debug "User context deleted: #{res.inspect}"
126
+
127
+ @context_id = nil
128
+ end
67
129
  end
68
130
  end
69
131
  end
@@ -22,15 +22,15 @@ module Bidi2pdf
22
22
 
23
23
  # Add listeners
24
24
 
25
- def on_message(&block) = socket_events.on(:message, &block)
25
+ def on_message(&) = socket_events.on(:message, &)
26
26
 
27
- def on_event(name, &block) = session_events.on(name, &block)
27
+ def on_event(name, &) = session_events.on(name, &)
28
28
 
29
- def on_open(&block) = socket_events.on(:open, &block)
29
+ def on_open(&) = socket_events.on(:open, &)
30
30
 
31
- def on_close(&block) = socket_events.on(:close, &block)
31
+ def on_close(&) = socket_events.on(:close, &)
32
32
 
33
- def on_error(&block) = socket_events.on(:error, &block)
33
+ def on_error(&) = socket_events.on(:error, &)
34
34
 
35
35
  def remove_message_listener(block) = socket_events.off(:message, block)
36
36
 
@@ -52,10 +52,10 @@ module Bidi2pdf
52
52
  method = data["method"]
53
53
 
54
54
  if method
55
- Bidi2pdf.logger.debug "Dispatching session event: #{method}"
55
+ Bidi2pdf.logger.debug3 "Dispatching session event: #{method}"
56
56
  that.session_events.dispatch(method, data)
57
57
  else
58
- Bidi2pdf.logger.debug "Dispatching socket message"
58
+ Bidi2pdf.logger.debug3 "Dispatching socket message"
59
59
  that.socket_events.dispatch(:message, data)
60
60
  end
61
61
  end
@@ -5,13 +5,17 @@ require "securerandom"
5
5
 
6
6
  module Bidi2pdf
7
7
  class ChromedriverManager
8
- attr_reader :port, :pid, :started
8
+ include Chromedriver::Binary::Platform
9
9
 
10
- def initialize(port: 0, headless: true)
10
+ attr_reader :port, :pid, :started, :headless, :chrome_args, :shutdown_mutex
11
+
12
+ def initialize(port: 0, headless: true, chrome_args: Bidi::Session::DEFAULT_CHROME_ARGS)
11
13
  @port = port
12
14
  @headless = headless
13
15
  @session = nil
14
16
  @started = false
17
+ @chrome_args = chrome_args
18
+ @shutdown_mutex ||= Mutex.new
15
19
  end
16
20
 
17
21
  def start
@@ -23,11 +27,7 @@ module Bidi2pdf
23
27
  cmd = build_cmd
24
28
  Bidi2pdf.logger.info "Starting Chromedriver with command: #{cmd}"
25
29
 
26
- r, w = IO.pipe
27
- @pid = Process.spawn(cmd, out: w, err: w)
28
- w.close # close writer in parent
29
-
30
- parse_port_from_output(r)
30
+ spawn_process(cmd)
31
31
 
32
32
  Bidi2pdf.logger.info "Started Chromedriver on port #{@port}, PID #{@pid}"
33
33
  wait_until_chromedriver_ready
@@ -40,7 +40,7 @@ module Bidi2pdf
40
40
  def session
41
41
  return unless @started
42
42
 
43
- @session ||= Bidi::Session.new(session_url: session_url, headless: @headless)
43
+ @session ||= Bidi::Session.new(session_url: session_url, headless: @headless, chrome_args: @chrome_args)
44
44
  end
45
45
 
46
46
  def session_url
@@ -50,27 +50,54 @@ module Bidi2pdf
50
50
  end
51
51
 
52
52
  def stop(timeout: 5)
53
- return unless @pid
53
+ shutdown_mutex.synchronize do
54
+ return unless @pid
54
55
 
55
- @started = false
56
+ @started = false
56
57
 
57
- close_session
58
+ close_session
58
59
 
59
- debug_show_all_children
60
+ debug_show_all_children
60
61
 
61
- old_childprocesses = term_chromedriver
62
+ old_childprocesses = term_chromedriver
62
63
 
63
- detect_zombie_processes old_childprocesses
64
+ detect_zombie_processes old_childprocesses
64
65
 
65
- return unless process_alive?
66
+ return unless process_alive?
66
67
 
67
- kill_chromedriver timeout: timeout
68
- ensure
69
- @pid = nil
68
+ kill_chromedriver timeout: timeout
69
+ ensure
70
+ @pid = nil
71
+ @started = false
72
+ end
70
73
  end
71
74
 
72
75
  private
73
76
 
77
+ def spawn_process(cmd)
78
+ r, w = IO.pipe
79
+
80
+ options = {
81
+ out: w,
82
+ err: w,
83
+ close_others: true,
84
+ chdir: Dir.tmpdir
85
+ }
86
+
87
+ if platform == "win"
88
+ options[:new_pgroup] = true
89
+ else
90
+ options[:pgroup] = true
91
+ end
92
+
93
+ env = {}
94
+
95
+ @pid = Process.spawn(env, cmd, **options)
96
+ w.close # close writer in parent
97
+
98
+ parse_port_from_output(r)
99
+ end
100
+
74
101
  def detect_zombie_processes(old_childprocesses)
75
102
  Bidi2pdf.logger.debug "Old child processes for #{@pid}: #{old_childprocesses.map(&:pid).join(", ")}"
76
103
 
@@ -99,7 +126,7 @@ module Bidi2pdf
99
126
  Bidi2pdf::ProcessTree.new(@pid).traverse do |process, level|
100
127
  indent = " " * level
101
128
  prefix = level.zero? ? "" : "└─ "
102
- Bidi2pdf.logger.debug "#{indent}#{prefix}PID #{process.pid} (#{process.name})"
129
+ Bidi2pdf.logger.debug2 "#{indent}#{prefix}PID #{process.pid} (#{process.name})"
103
130
  end
104
131
  end
105
132
 
@@ -114,7 +141,7 @@ module Bidi2pdf
114
141
  Bidi2pdf::ProcessTree.new(@pid).children(@pid).tap do |_child_processes|
115
142
  Bidi2pdf.logger.info "Stopping Chromedriver (PID #{@pid})"
116
143
 
117
- Process.kill("TERM", @pid)
144
+ Process.kill("TERM", -@pid) # - meanskill linux pgroup
118
145
  end
119
146
  rescue Errno::ESRCH
120
147
  Bidi2pdf.logger.debug "Process already gone"
@@ -159,7 +186,7 @@ module Bidi2pdf
159
186
  def parse_port_from_output(io, timeout: 5)
160
187
  Thread.new do
161
188
  io.each_line do |line|
162
- Bidi2pdf.logger.debug line.chomp
189
+ Bidi2pdf.logger.debug1 line.chomp
163
190
 
164
191
  next unless line =~ /ChromeDriver was started successfully on port (\d+)/
165
192
 
data/lib/bidi2pdf/cli.rb CHANGED
@@ -51,6 +51,17 @@ module Bidi2pdf
51
51
  option :log_level,
52
52
  type: :string,
53
53
  default: "info", enum: %w[debug info warn error fatal unknown], desc: "Set log level"
54
+ verbosity_levels = Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS.keys.sort_by { |k| Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS[k] }
55
+ option :verbosity,
56
+ type: :string,
57
+ default: verbosity_levels.first, enum: Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS.keys.sort_by { |k| Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS[k] }.map(&:to_s),
58
+ desc: "Set debug verbosity level", aliases: "-v"
59
+ option :log_network_traffic, type: :boolean, default: false, desc: "Log network traffic", aliases: "-n"
60
+ option :network_log_format,
61
+ type: :string,
62
+ default: "console",
63
+ enum: %w[console pdf],
64
+ desc: "Choose network log format: console or pdf", aliases: "-f"
54
65
 
55
66
  option :background, type: :boolean, default: true, desc: "Print background graphics"
56
67
  option :margin_top, type: :numeric, default: 1.0, desc: "Top margin in inches"
@@ -64,6 +75,12 @@ module Bidi2pdf
64
75
  option :scale, type: :numeric, default: 1.0, desc: "Scale between 0.1 and 2.0"
65
76
  option :shrink_to_fit, type: :boolean, default: true, desc: "Shrink content to fit page"
66
77
 
78
+ class << self
79
+ def exit_on_failure?
80
+ true
81
+ end
82
+ end
83
+
67
84
  def render
68
85
  load_config
69
86
 
@@ -198,16 +215,20 @@ module Bidi2pdf
198
215
  headless: merged_options[:headless],
199
216
  wait_window_loaded: merged_options[:wait_window_loaded],
200
217
  wait_network_idle: merged_options[:wait_network_idle],
201
- print_options: print_options
218
+ print_options: print_options,
219
+ network_log_format: merged_options[:network_log_format]
202
220
  )
203
221
  end
204
222
  end
205
223
 
206
- # rubocop:enable Metrics/AbcSize
207
-
208
224
  def configure
209
225
  Bidi2pdf.configure do |config|
210
226
  config.logger.level = log_level
227
+
228
+ config.logger.verbosity = merged_options[:verbosity]
229
+
230
+ config.network_events_logger.level = Logger::INFO if merged_options[:log_network_traffic]
231
+
211
232
  config.default_timeout = merged_options[:default_timeout]
212
233
 
213
234
  Chromedriver::Binary.configure do |c|
@@ -216,6 +237,8 @@ module Bidi2pdf
216
237
  end
217
238
  end
218
239
 
240
+ # rubocop: enable Metrics/MethodLength
241
+
219
242
  def log_level
220
243
  case merged_options[:log_level]
221
244
  when "debug" then Logger::DEBUG
@@ -250,3 +273,4 @@ module Bidi2pdf
250
273
  end
251
274
  end
252
275
  end
276
+ # rubocop:enable Metrics/AbcSize
data/lib/bidi2pdf/dsl.rb CHANGED
@@ -4,7 +4,39 @@ require "bidi2pdf"
4
4
 
5
5
  module Bidi2pdf
6
6
  module DSL
7
+ # Provides a DSL for managing browser sessions and tabs
8
+ # using the Bidi2pdf library. This module includes a method to create and manage
9
+ # browser tabs within a controlled session.
10
+
7
11
  # rubocop: disable Metrics/AbcSize
12
+ #
13
+ # Executes a block of code within the context of a browser tab.
14
+ #
15
+ # This method handles the setup and teardown of a browser session, user context,
16
+ # browser window, and tab. It ensures that resources are properly cleaned up
17
+ # after the block is executed.
18
+ #
19
+ # @param [String, nil] remote_browser_url The URL of a remote browser to connect to.
20
+ # If provided, the session will connect to this browser in headless mode.
21
+ # @param [Integer] port The port to use for the local browser session. Defaults to 0 (chooses a random port).
22
+ # @param [Boolean] headless Whether to run the browser in headless mode. Defaults to true.
23
+ # @param [Array<String>] chrome_args Additional arguments to pass to the Chrome browser.
24
+ # Defaults to the `DEFAULT_CHROME_ARGS` from the `Bidi2pdf::Bidi::Session` class.
25
+ #
26
+ # @yield [tab] The browser tab created within the session.
27
+ # @yieldparam [Object] tab The browser tab object.
28
+ #
29
+ # @example Using a local browser session
30
+ # Bidi2pdf::DSL.with_tab(port: 9222, headless: false) do |tab|
31
+ # # Perform actions with the tab
32
+ # end
33
+ #
34
+ # @example Using a remote browser session
35
+ # Bidi2pdf::DSL.with_tab(remote_browser_url: "http://remote-browser:9222/session") do |tab|
36
+ # # Perform actions with the tab
37
+ # end
38
+ #
39
+ # @return [void]
8
40
  def self.with_tab(remote_browser_url: nil, port: 0, headless: true, chrome_args: Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS.dup)
9
41
  manager = nil
10
42
  session = nil
@@ -35,6 +67,7 @@ module Bidi2pdf
35
67
  ensure
36
68
  tab&.close
37
69
  window&.close
70
+ context&.close
38
71
  session&.close
39
72
  manager&.stop
40
73
  end
@@ -5,10 +5,40 @@ require_relative "session_runner"
5
5
  require_relative "bidi/session"
6
6
 
7
7
  module Bidi2pdf
8
+ # Represents a launcher for managing browser sessions and executing tasks
9
+ # using the Bidi2pdf library. This class handles the setup and teardown
10
+ # of browser sessions, as well as the execution of tasks within those sessions.
11
+ #
12
+ # @example Launching a session
13
+ # launcher = Bidi2pdf::Launcher.new(
14
+ # url: "http://example.com",
15
+ # inputfile: "input.pdf",
16
+ # output: "output.pdf",
17
+ # cookies: [],
18
+ # headers: {},
19
+ # auth: nil,
20
+ # headless: true
21
+ # )
22
+ # launcher.launch
23
+ # launcher.stop
24
+ #
25
+ # @param [String] url The URL to navigate to in the browser session.
26
+ # @param [String] inputfile The path to the input file to be processed.
27
+ # @param [String] output The path to the output file to be generated.
28
+ # @param [Array<Hash>] cookies An array of cookies to set in the browser session.
29
+ # @param [Hash] headers A hash of HTTP headers to include in the browser session.
30
+ # @param [Hash, nil] auth Authentication credentials (e.g., username and password).
31
+ # @param [Boolean] headless Whether to run the browser in headless mode. Defaults to true.
32
+ # @param [Integer] port The port to use for the browser session. Defaults to 0.
33
+ # @param [Boolean] wait_window_loaded Whether to wait for the window to fully load. Defaults to false.
34
+ # @param [Boolean] wait_network_idle Whether to wait for the network to become idle. Defaults to false.
35
+ # @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
36
+ # @param [String, nil] remote_browser_url The URL of a remote browser to connect to. Defaults to nil.
37
+ # @param [Symbol] network_log_format The format for network logs. Defaults to :console.
8
38
  class Launcher
9
39
  # rubocop:disable Metrics/ParameterLists
10
40
  def initialize(url:, inputfile:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
11
- wait_network_idle: false, print_options: {}, remote_browser_url: nil)
41
+ wait_network_idle: false, print_options: {}, remote_browser_url: nil, network_log_format: :console)
12
42
  @url = url
13
43
  @inputfile = inputfile
14
44
  @port = port
@@ -23,6 +53,7 @@ module Bidi2pdf
23
53
  @print_options = print_options || {}
24
54
  @remote_browser_url = remote_browser_url
25
55
  @custom_session = nil
56
+ @network_log_format = network_log_format
26
57
  end
27
58
 
28
59
  # rubocop:enable Metrics/ParameterLists
@@ -38,7 +69,8 @@ module Bidi2pdf
38
69
  auth: @auth,
39
70
  wait_window_loaded: @wait_window_loaded,
40
71
  wait_network_idle: @wait_network_idle,
41
- print_options: @print_options
72
+ print_options: @print_options,
73
+ network_log_format: @network_log_format
42
74
  )
43
75
  runner.run
44
76
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ # rubocop: disable Lint/RescueException
5
+ module Notifications
6
+ class Event
7
+ attr_reader :name, :transaction_id
8
+ attr_accessor :payload
9
+
10
+ def initialize(name, start, ending, transaction_id, payload)
11
+ @name = name
12
+ @payload = payload
13
+ @time = start ? start.to_f * 1_000.0 : start
14
+ @transaction_id = transaction_id
15
+ @end = ending ? ending.to_f * 1_000.0 : ending
16
+ end
17
+
18
+ def record # :nodoc:
19
+ start!
20
+ begin
21
+ yield payload if block_given?
22
+ rescue Exception => e
23
+ payload[:exception] = [e.class.name, e.message]
24
+ payload[:exception_object] = e
25
+ raise e
26
+ ensure
27
+ finish!
28
+ end
29
+ end
30
+
31
+ def start! = @time = now
32
+
33
+ def finish! = @end = now
34
+
35
+ def duration = @end - @time
36
+
37
+ def time
38
+ @time / 1000.0 if @time
39
+ end
40
+
41
+ def end
42
+ @end / 1000.0 if @end
43
+ end
44
+
45
+ private
46
+
47
+ def now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
48
+ end
49
+ end
50
+ end
51
+
52
+ # rubocop: enable Lint/RescueException
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Bidi2pdf
6
+ # This module provides a way to instrument events in the Bidi2pdf library.
7
+ # It it's heavyly inspired by ActiveSupport::Notifications.
8
+ # and thought to be used in a similar way.
9
+ # In Rails environment, ActiveSupport::Notifications should be use instead.
10
+ # via configuration: Bidi2pdf.notification_service = ActiveSupport::Notifications
11
+
12
+ # rubocop: disable Lint/RescueException, Lint/SuppressedException
13
+ module Notifications
14
+ class Instrumenter
15
+ attr_reader :id
16
+
17
+ def initialize
18
+ @id = SecureRandom.uuid
19
+ end
20
+
21
+ def notify(name, payload, &)
22
+ event = create_event(name, payload)
23
+ result = nil
24
+ begin
25
+ result = event.record(&)
26
+ rescue Exception => e
27
+ end
28
+
29
+ subscriber_exceptions = notify_subscribers(name, event)
30
+
31
+ raise Bidi2pdf::NotificationsError.new(subscriber_exceptions), cause: subscriber_exceptions.first if subscriber_exceptions.any?
32
+ raise e if e
33
+
34
+ result
35
+ end
36
+
37
+ private
38
+
39
+ def create_event(name, payload)
40
+ Event.new(name, nil, nil, @id, payload)
41
+ end
42
+
43
+ # rubocop:disable Style/CaseEquality
44
+ def notify_subscribers(name, event)
45
+ exceptions = []
46
+
47
+ Notifications.subscribers.each do |pattern, blocks|
48
+ next unless pattern === name
49
+
50
+ blocks.each do |subscriber|
51
+ subscriber.call(event)
52
+ rescue Exception => e
53
+ exceptions << e
54
+ end
55
+ end
56
+
57
+ exceptions
58
+ end
59
+
60
+ # rubocop:enable Style/CaseEquality
61
+ end
62
+ end
63
+ end
64
+
65
+ # rubocop: enable Lint/RescueException, Lint/SuppressedException