mimic-browser 0.1.0 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +13 -12
- data/lib/mimic_sdk/ferrum.rb +130 -18
- data/lib/mimic_sdk/generated.rb +2477 -121
- data/lib/mimic_sdk/runtime-lock.json +18 -18
- data/lib/mimic_sdk/runtime.rb +11 -2
- data/lib/mimic_sdk/transport.rb +59 -4
- data/lib/mimic_sdk.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37f584d860c127be652e6c4979dca16dae2dda07263f3a306b9324416071a272
|
|
4
|
+
data.tar.gz: 2787d67161bac57dd660881b2250ef446dd703117e4591dc2942978132e47a6b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9ca3db0cbac5f553e9bdb9d138006cdfe366b17152ca2ace3a577b9d80f743260c3d338aecc87985e9efa45eec5e9ca43d7cb8b67193c52e85d5a505eab0af4
|
|
7
|
+
data.tar.gz: 1bf217c272574b736d016c0a34487e84734d938b70bc0d0be779b1d571f42f5c4bba390725c48613147a11c9d1b254fd89530e9cec61dd1ae1ae3db827634e71
|
data/README.md
CHANGED
|
@@ -3,18 +3,14 @@
|
|
|
3
3
|
Package `mimic-browser` requires Ruby 3.2+. Ferrum is optional; require
|
|
4
4
|
`mimic_sdk/ferrum` to select it. Core import is inert.
|
|
5
5
|
|
|
6
|
-
Install
|
|
7
|
-
published yet:
|
|
6
|
+
Install the SDK from RubyGems and the optional Ferrum client:
|
|
8
7
|
|
|
9
8
|
```sh
|
|
10
|
-
|
|
11
|
-
cd sdk/ruby
|
|
12
|
-
gem build mimic-browser.gemspec
|
|
13
|
-
gem install ./mimic-browser-0.1.0.gem --no-document
|
|
9
|
+
gem install mimic-browser --no-document
|
|
14
10
|
gem install ferrum --version 0.18.0 --no-document
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
The bundled v0.2.
|
|
13
|
+
The bundled v0.2.4 runtime includes the CSS and navigation lifecycle support
|
|
18
14
|
required by Ferrum. The first launch downloads and verifies that exact runtime;
|
|
19
15
|
later launches reuse the persistent cache. No executable path is required.
|
|
20
16
|
|
|
@@ -39,8 +35,10 @@ both the browser endpoint and `/devtools/page/*` on the same origin.
|
|
|
39
35
|
|
|
40
36
|
`new_context(profile:, media:, resource_policy:, proxy:)` applies Mimic settings
|
|
41
37
|
before returning the native context. A managed profile uses the runtime's
|
|
42
|
-
configure-context bridge. `for_page(page)`
|
|
43
|
-
the owned raw connection.
|
|
38
|
+
configure-context bridge. `for_page(page)` reuses one page capability handle on
|
|
39
|
+
the owned raw connection. Call `handle.close` to detach it early; the native Page
|
|
40
|
+
remains open. Closing the Page or integration invalidates the handle and removes
|
|
41
|
+
its cache entry. Framework session IDs are never copied across sockets.
|
|
44
42
|
|
|
45
43
|
`media:` also accepts a callable. It receives
|
|
46
44
|
`ContextSetup(browser_context_id:, mimic:)` after native Context creation and
|
|
@@ -64,7 +62,10 @@ the adapter's ownership mutex; a failing callback disposes its new Context.
|
|
|
64
62
|
Media configuration alone keeps ordinary CDP emulation available; only an
|
|
65
63
|
explicit environment profile or proxy selects managed Context configuration.
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
Runtime and adapter methods expose explicit keyword parameters. Generated models
|
|
66
|
+
also expose named constructors, for example
|
|
67
|
+
`Generated::MediaConfiguration.new(devices: [])`, and documented field/result
|
|
68
|
+
types for editor completion. Typed capabilities use snake_case methods. `UNSET` omits a
|
|
68
69
|
value, while `nil` remains JSON null. Raw wire names retain their exact spelling:
|
|
69
70
|
|
|
70
71
|
```ruby
|
|
@@ -79,9 +80,9 @@ dispatcher serves generated and experimental calls. `ProtocolError` retains
|
|
|
79
80
|
after dispatch does not imply rollback and does not cause automatic retries.
|
|
80
81
|
|
|
81
82
|
`RuntimeManager.new(runtime_version:, lock_file:, executable_path:, runtime_dir:,
|
|
82
|
-
allow_download:, archive_path:, startup_timeout:, cancelled:)` follows the shared
|
|
83
|
+
allow_download:, archive_path:, startup_timeout:, lock_timeout:, cancelled:)` follows the shared
|
|
83
84
|
runtime contract. It provides `resolve_lock`, `install`, `launch`, `list`,
|
|
84
|
-
`verify` and explicit `prune`. The packaged pin is v0.2.
|
|
85
|
+
`verify` and explicit `prune`. The packaged pin is v0.2.4. Exact archives and
|
|
85
86
|
executables are hash-verified; all languages reuse the same OS cache and leases.
|
|
86
87
|
Use `mimic-sdk install --offline --archive /path/to/official.tar.gz`, `list`,
|
|
87
88
|
`verify`, or `lock` for deliberate cache operations.
|
data/lib/mimic_sdk/ferrum.rb
CHANGED
|
@@ -8,15 +8,30 @@ end
|
|
|
8
8
|
|
|
9
9
|
module MimicSDK
|
|
10
10
|
module Ferrum
|
|
11
|
+
# Identity and typed capabilities for the context being configured.
|
|
12
|
+
# @!attribute [rw] browser_context_id
|
|
13
|
+
# @return [String]
|
|
14
|
+
# @!attribute [rw] mimic
|
|
15
|
+
# @return [MimicSDK::Client]
|
|
11
16
|
ContextSetup = Struct.new(:browser_context_id, :mimic, keyword_init: true)
|
|
12
17
|
|
|
13
18
|
class Session
|
|
14
|
-
|
|
19
|
+
# @return [::Ferrum::Browser]
|
|
20
|
+
attr_reader :browser
|
|
21
|
+
# @return [MimicSDK::Client]
|
|
22
|
+
attr_reader :mimic
|
|
23
|
+
# @return [MimicSDK::RuntimeProcess, nil] nil when attached to an external runtime
|
|
24
|
+
attr_reader :runtime
|
|
15
25
|
|
|
26
|
+
# @param endpoint [String] HTTP or browser WebSocket endpoint
|
|
27
|
+
# @param runtime [MimicSDK::RuntimeProcess, nil]
|
|
28
|
+
# @param timeout [Numeric] native client timeout in seconds
|
|
16
29
|
def initialize(endpoint, runtime: nil, timeout: 30)
|
|
17
30
|
@runtime = runtime
|
|
18
31
|
@contexts = []
|
|
32
|
+
@page_clients = {}
|
|
19
33
|
@mutex = Mutex.new
|
|
34
|
+
@close_changed = ConditionVariable.new
|
|
20
35
|
@transport = runtime ? runtime.transport : Transport.new(endpoint, timeout: timeout)
|
|
21
36
|
@mimic = Client.new(@transport)
|
|
22
37
|
version = @mimic.get_version
|
|
@@ -27,6 +42,23 @@ module MimicSDK
|
|
|
27
42
|
@browser = ::Ferrum::Browser.new(
|
|
28
43
|
ws_url: @transport.url, timeout: timeout, process_timeout: timeout, flatten: false
|
|
29
44
|
)
|
|
45
|
+
events, @unsubscribe = @transport.subscribe
|
|
46
|
+
@attachment_events = Thread.new do
|
|
47
|
+
while (event = events.pop)
|
|
48
|
+
next unless event['method'] == 'Target.detachedFromTarget'
|
|
49
|
+
@mutex.synchronize do
|
|
50
|
+
@page_clients.delete_if do |_target, client|
|
|
51
|
+
detached = client.session_id == event.dig('params', 'sessionId')
|
|
52
|
+
detached && client.invalidate
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
ensure
|
|
57
|
+
@mutex.synchronize do
|
|
58
|
+
# A pending explicit detach remains owned until its reply/failure.
|
|
59
|
+
@page_clients.delete_if { |_target, client| client.invalidate }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
30
62
|
rescue StandardError
|
|
31
63
|
close
|
|
32
64
|
raise
|
|
@@ -34,6 +66,12 @@ module MimicSDK
|
|
|
34
66
|
|
|
35
67
|
# Returns Ferrum::Context, not an SDK automation wrapper. All configuration
|
|
36
68
|
# is complete before a user Page can be created through the returned object.
|
|
69
|
+
# @param profile [String, MimicSDK::Generated::GenerateProfileSelection, Hash]
|
|
70
|
+
# @param media [MimicSDK::Generated::MediaConfiguration, Hash, Proc, nil]
|
|
71
|
+
# A Proc receives ContextSetup and returns MediaConfiguration or its wire Hash.
|
|
72
|
+
# @param resource_policy [MimicSDK::Generated::ResourcePolicy, Hash, nil]
|
|
73
|
+
# @param proxy [MimicSDK::Generated::Proxy, Hash, nil]
|
|
74
|
+
# @return [::Ferrum::Context]
|
|
37
75
|
def new_context(profile: UNSET, media: nil, resource_policy: nil, proxy: nil)
|
|
38
76
|
context = @mutex.synchronize do
|
|
39
77
|
raise RuntimeError, 'Integration session closed' if @closed
|
|
@@ -74,34 +112,102 @@ module MimicSDK
|
|
|
74
112
|
end
|
|
75
113
|
end
|
|
76
114
|
|
|
115
|
+
# @param page [::Ferrum::Page]
|
|
116
|
+
# @return [MimicSDK::PageClient] cached until detached; close releases only this attachment
|
|
77
117
|
def for_page(page)
|
|
78
118
|
raise ArgumentError, 'Expected an actual Ferrum::Page' unless page.is_a?(::Ferrum::Page)
|
|
79
|
-
|
|
80
|
-
|
|
119
|
+
loop do
|
|
120
|
+
client = @mutex.synchronize do
|
|
121
|
+
raise RuntimeError, 'Integration session closed' if @closed
|
|
122
|
+
@page_clients[page.target_id] ||= begin
|
|
123
|
+
session = @transport.call_raw('Target.attachToTarget', { 'targetId' => page.target_id, 'flatten' => true })
|
|
124
|
+
PageClient.new(@transport, session_id: session.fetch('sessionId'), detach: method(:detach_page_client))
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
return client unless client.closed?
|
|
128
|
+
# Never replace a closing handle before its cleanup finishes. Waiting
|
|
129
|
+
# outside the Session mutex allows detach to release its ownership.
|
|
130
|
+
client.close
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def detach_page_client(client)
|
|
135
|
+
@transport.call_raw('Target.detachFromTarget', { 'sessionId' => client.session_id })
|
|
136
|
+
rescue ProtocolError => error
|
|
137
|
+
# The target can close between local invalidation and the detach command.
|
|
138
|
+
raise unless error.code == -32000 && error.message == 'No session with given id'
|
|
139
|
+
ensure
|
|
140
|
+
@mutex.synchronize { @page_clients.delete_if { |_target, owned| owned.equal?(client) } }
|
|
81
141
|
end
|
|
142
|
+
private :detach_page_client
|
|
82
143
|
|
|
83
144
|
def close
|
|
84
145
|
return unless @mutex
|
|
85
|
-
@mutex.synchronize do
|
|
86
|
-
|
|
146
|
+
contexts, clients = @mutex.synchronize do
|
|
147
|
+
@close_changed.wait(@mutex) while @closing
|
|
148
|
+
if @closed
|
|
149
|
+
raise @close_error if @close_error
|
|
150
|
+
return
|
|
151
|
+
end
|
|
87
152
|
@closed = true
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
153
|
+
@closing = true
|
|
154
|
+
owned = [@contexts, @page_clients.values]
|
|
155
|
+
@contexts = []
|
|
156
|
+
@page_clients = {}
|
|
157
|
+
owned
|
|
158
|
+
end
|
|
159
|
+
failure = nil
|
|
160
|
+
cleanup = lambda do |&operation|
|
|
161
|
+
operation.call
|
|
162
|
+
rescue StandardError => error
|
|
163
|
+
failure ||= error
|
|
164
|
+
end
|
|
165
|
+
begin
|
|
166
|
+
clients.each { |client| cleanup.call { client.close } }
|
|
167
|
+
contexts.each { |context| cleanup.call { context.dispose } }
|
|
168
|
+
cleanup.call { @browser&.quit } # A remote Ferrum browser owns no process.
|
|
169
|
+
cleanup.call { @runtime ? @runtime.close : @transport&.close }
|
|
170
|
+
ensure
|
|
171
|
+
clients.each(&:invalidate)
|
|
172
|
+
cleanup.call { @unsubscribe&.call }
|
|
173
|
+
cleanup.call { @attachment_events&.join unless @attachment_events == Thread.current }
|
|
174
|
+
@mutex.synchronize do
|
|
175
|
+
@close_error = failure
|
|
176
|
+
@closing = false
|
|
177
|
+
@close_changed.broadcast
|
|
96
178
|
end
|
|
97
179
|
end
|
|
180
|
+
raise failure if failure
|
|
98
181
|
nil
|
|
99
182
|
end
|
|
100
183
|
end
|
|
101
184
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
185
|
+
# @param runtime_version [String, nil]
|
|
186
|
+
# @param lock_file [String, nil]
|
|
187
|
+
# @param executable_path [String, nil]
|
|
188
|
+
# @param runtime_dir [String, nil]
|
|
189
|
+
# @param archive_path [String, nil]
|
|
190
|
+
# @param allow_download [Boolean] omitted to use the environment default
|
|
191
|
+
# @param startup_timeout [Numeric] seconds
|
|
192
|
+
# @param lock_timeout [Numeric] seconds
|
|
193
|
+
# @param cancelled [Proc, nil] returns true to cancel
|
|
194
|
+
# @param timeout [Numeric] native client timeout in seconds
|
|
195
|
+
# @yield [session] closes the session after the block
|
|
196
|
+
# @yieldparam session [MimicSDK::Ferrum::Session]
|
|
197
|
+
# @return [MimicSDK::Ferrum::Session] when called without a block;
|
|
198
|
+
# the block form returns the block's result instead
|
|
199
|
+
def self.launch(runtime_version: nil, lock_file: nil, executable_path: nil,
|
|
200
|
+
runtime_dir: nil, archive_path: nil, allow_download: UNSET,
|
|
201
|
+
startup_timeout: 30, lock_timeout: 120, cancelled: nil,
|
|
202
|
+
timeout: 30)
|
|
203
|
+
process = RuntimeManager.new(
|
|
204
|
+
runtime_version: runtime_version, lock_file: lock_file,
|
|
205
|
+
executable_path: executable_path, runtime_dir: runtime_dir,
|
|
206
|
+
archive_path: archive_path, allow_download: allow_download,
|
|
207
|
+
startup_timeout: startup_timeout, lock_timeout: lock_timeout,
|
|
208
|
+
cancelled: cancelled
|
|
209
|
+
).launch
|
|
210
|
+
session = Session.new(process.endpoint, runtime: process, timeout: timeout)
|
|
105
211
|
return session unless block_given?
|
|
106
212
|
begin
|
|
107
213
|
yield session
|
|
@@ -113,8 +219,14 @@ module MimicSDK
|
|
|
113
219
|
raise
|
|
114
220
|
end
|
|
115
221
|
|
|
116
|
-
|
|
117
|
-
|
|
222
|
+
# @param endpoint [String] HTTP or browser WebSocket endpoint
|
|
223
|
+
# @param timeout [Numeric] native client timeout in seconds
|
|
224
|
+
# @yield [session] closes the session after the block
|
|
225
|
+
# @yieldparam session [MimicSDK::Ferrum::Session]
|
|
226
|
+
# @return [MimicSDK::Ferrum::Session] when called without a block;
|
|
227
|
+
# the block form returns the block's result instead
|
|
228
|
+
def self.connect(endpoint, timeout: 30)
|
|
229
|
+
session = Session.new(endpoint, timeout: timeout)
|
|
118
230
|
return session unless block_given?
|
|
119
231
|
begin
|
|
120
232
|
yield session
|