cineworld_uk 4.0.0 → 4.2.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +26 -0
  3. data/CHANGELOG.md +26 -0
  4. data/README.md +97 -6
  5. data/Rakefile +3 -23
  6. data/cineworld_uk.gemspec +2 -1
  7. data/lib/cineworld_uk/internal/api_response.rb +7 -30
  8. data/lib/cineworld_uk/internal/guest_token.rb +30 -0
  9. data/lib/cineworld_uk/internal/parser/api/film.rb +6 -0
  10. data/lib/cineworld_uk/internal/parser/api/performance.rb +5 -0
  11. data/lib/cineworld_uk/internal/title_sanitizer.rb +2 -10
  12. data/lib/cineworld_uk/performance.rb +4 -1
  13. data/lib/cineworld_uk/version.rb +2 -2
  14. data/lib/cineworld_uk.rb +8 -1
  15. data/test/fixtures/api/showtimes-014.json +67 -0
  16. data/test/fixtures/api/sites.json +52 -0
  17. data/test/lib/cineworld_uk/cinema_test.rb +63 -223
  18. data/test/lib/cineworld_uk/internal/api_response_test.rb +28 -107
  19. data/test/lib/cineworld_uk/internal/fixture_shape_test.rb +14 -0
  20. data/test/lib/cineworld_uk/internal/guest_token_test.rb +87 -0
  21. data/test/lib/cineworld_uk/internal/parser/api/cinema_address_test.rb +44 -85
  22. data/test/lib/cineworld_uk/internal/parser/api/film_test.rb +53 -131
  23. data/test/lib/cineworld_uk/internal/parser/api/performance_test.rb +27 -45
  24. data/test/lib/cineworld_uk/internal/title_sanitizer_test.rb +75 -126
  25. data/test/lib/cineworld_uk/performance_test.rb +153 -180
  26. data/test/lib/cineworld_uk/version_test.rb +3 -3
  27. data/test/live/api_shape_test.rb +22 -0
  28. data/test/live/integration_test.rb +13 -43
  29. data/test/live/live_helper.rb +11 -0
  30. data/test/package_test.rb +56 -0
  31. data/test/support/api_shapes.rb +48 -0
  32. data/test/support/vista_fixtures.rb +58 -0
  33. data/test/test_helper.rb +3 -0
  34. metadata +37 -14
  35. data/test/fixtures/api/cinema-list.json +0 -1
  36. data/test/fixtures/api/dates-14.json +0 -1
  37. data/test/fixtures/api/performances-tomorrow-14.json +0 -1
  38. data/test/fixtures/api/vista-showtimes-brighton.json +0 -1
  39. data/test/support/fixture_reader.rb +0 -32
@@ -1,249 +1,89 @@
1
1
  require_relative '../../test_helper'
2
- require_relative '../../support/fixture_reader'
3
-
4
- describe CineworldUk::Cinema do
5
- include Support::FixtureReader
6
2
 
7
- let(:described_class) { CineworldUk::Cinema }
8
- let(:api_response) { Minitest::Mock.new }
3
+ class CineworldUk::CinemaTest < Minitest::Test
4
+ include Support::VistaFixtures
9
5
 
10
- before { WebMock.disable_net_connect! }
11
- after { WebMock.allow_net_connect! }
12
-
13
- describe '.all' do
14
- subject { described_class.all }
15
-
16
- before { api_response.expect(:cinema_list, cinema_list_json) }
17
-
18
- it 'returns an Array of CineworldUK::Cinemas' do
19
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
20
- _(subject).must_be_instance_of(Array)
21
- subject.each do |value|
22
- _(value).must_be_instance_of(CineworldUk::Cinema)
23
- end
24
- end
25
- end
26
-
27
- it 'returns the correctly sized array' do
28
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
29
- _(subject.size).must_equal(87)
30
- end
31
- end
32
- end
33
-
34
- describe '#adr' do
35
- subject { described_class.new(id).adr }
36
-
37
- before do
38
- api_response.expect(:cinema_list, cinema_list_json)
39
- end
40
-
41
- describe 'Brighton (14)' do
42
- let(:id) { 14 }
43
-
44
- it 'returns the address hash' do
45
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
46
- _(subject).must_equal(
47
- street_address: 'Brighton Marina Village',
48
- extended_address: nil,
49
- locality: 'Brighton',
50
- region: nil,
51
- postal_code: 'BN2 5UF',
52
- country: 'United Kingdom'
53
- )
54
- end
55
- end
56
- end
57
- end
58
-
59
- describe '#address' do
60
- subject { described_class.new(id).address }
61
-
62
- before do
63
- api_response.expect(:cinema_list, cinema_list_json)
64
- end
65
-
66
- describe 'Brighton (14)' do
67
- let(:id) { 14 }
68
-
69
- it 'returns the address hash' do
70
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
71
- _(subject).must_equal(
72
- street_address: 'Brighton Marina Village',
73
- extended_address: nil,
74
- locality: 'Brighton',
75
- region: nil,
76
- postal_code: 'BN2 5UF',
77
- country: 'United Kingdom'
78
- )
79
- end
80
- end
81
- end
82
- end
83
-
84
- describe '#extended_address' do
85
- subject { described_class.new(id).extended_address }
86
-
87
- before do
88
- api_response.expect(:cinema_list, cinema_list_json)
89
- end
90
-
91
- describe 'Brighton (14)' do
92
- let(:id) { 14 }
93
-
94
- it 'returns the address hash' do
95
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
96
- _(subject).must_equal('')
97
- end
98
- end
99
- end
6
+ def setup
7
+ setup_vista
8
+ stub_sites
100
9
  end
101
10
 
102
- describe '#full_name' do
103
- subject { described_class.new(id).full_name }
104
-
105
- before { api_response.expect(:cinema_list, cinema_list_json) }
106
-
107
- describe 'simple name (Brighton)' do
108
- let(:id) { 14 }
109
-
110
- it 'returns the brand in the name' do
111
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
112
- _(subject).must_equal 'Cineworld Brighton'
113
- end
114
- end
115
- end
116
-
117
- describe 'simple name (Brighton) called with String' do
118
- let(:id) { '14' }
119
-
120
- it 'returns the brand in the name' do
121
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
122
- _(subject).must_equal 'Cineworld Brighton'
123
- end
124
- end
125
- end
126
-
127
- describe 'complex name (Glasgow Silverburn)' do
128
- let(:id) { 88 }
129
-
130
- it 'returns the brand in the name' do
131
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
132
- _(subject).must_equal 'Cineworld Glasgow: Silverburn'
133
- end
134
- end
135
- end
11
+ def teardown
12
+ teardown_vista
13
+ super
136
14
  end
137
15
 
138
- describe '#locality' do
139
- subject { described_class.new(id).locality }
140
-
141
- before do
142
- api_response.expect(:cinema_list, cinema_list_json)
143
- end
144
-
145
- describe 'Brighton (14)' do
146
- let(:id) { 14 }
147
-
148
- it 'returns the town/city' do
149
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
150
- _(subject).must_equal 'Brighton'
151
- end
152
- end
153
- end
154
-
155
- describe 'London - Leicester Square (103)' do
156
- let(:id) { 103 }
16
+ def test_all_returns_cinemas
17
+ cinemas = CineworldUk::Cinema.all
157
18
 
158
- it 'returns borough of London' do
159
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
160
- _(subject).must_equal 'London'
161
- end
162
- end
163
- end
164
- end
19
+ assert(cinemas.all? { |cinema| cinema.instance_of?(CineworldUk::Cinema) })
20
+ assert_equal [14, 88, 103, 999], cinemas.map(&:id)
21
+ end
165
22
 
166
- describe '#postal_code' do
167
- subject { described_class.new(id).postal_code }
168
-
169
- before do
170
- api_response.expect(:cinema_list, cinema_list_json)
171
- end
172
-
173
- describe 'Brighton (14)' do
174
- let(:id) { 14 }
23
+ def test_all_fetches_sites_once
24
+ CineworldUk::Cinema.all
25
+ CineworldUk::Cinema.new(14).name
175
26
 
176
- it 'returns the post code' do
177
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
178
- _(subject).must_equal 'BN2 5UF'
179
- end
180
- end
181
- end
27
+ assert_requested :get, "#{BASE_URL}sites", times: 1
182
28
  end
183
29
 
184
- describe '#region' do
185
- subject { described_class.new(id).region }
186
-
187
- before do
188
- api_response.expect(:cinema_list, cinema_list_json)
189
- end
30
+ def test_adr
31
+ expected = {
32
+ street_address: '1 Example Marina',
33
+ extended_address: nil,
34
+ locality: 'Brighton',
35
+ region: 'East Sussex',
36
+ postal_code: 'BN2 0ZZ',
37
+ country: 'United Kingdom'
38
+ }
39
+
40
+ assert_equal expected, CineworldUk::Cinema.new(14).adr
41
+ assert_equal expected, CineworldUk::Cinema.new(14).address
42
+ end
190
43
 
191
- describe 'no region - Brighton (14)' do
192
- let(:id) { 14 }
44
+ def test_extended_address_is_empty
45
+ assert_equal '', CineworldUk::Cinema.new(14).extended_address
46
+ end
193
47
 
194
- it 'returns the empty string is none exists' do
195
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
196
- _(subject).must_equal ''
197
- end
198
- end
199
- end
48
+ def test_full_name
49
+ assert_equal 'Cineworld Brighton', CineworldUk::Cinema.new(14).full_name
50
+ end
200
51
 
201
- describe 'London - Leicester Square (103)' do
202
- let(:id) { 103 }
52
+ def test_full_name_accepts_string_id
53
+ assert_equal 'Cineworld Brighton', CineworldUk::Cinema.new('14').full_name
54
+ end
203
55
 
204
- it 'returns "London"' do
205
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
206
- _(subject).must_equal 'London'
207
- end
208
- end
209
- end
210
- end
211
-
212
- describe '#street_address' do
213
- subject { described_class.new(id).street_address }
56
+ def test_full_name_with_multiple_words
57
+ assert_equal 'Cineworld Glasgow Silverburn', CineworldUk::Cinema.new(88).full_name
58
+ end
214
59
 
215
- before do
216
- api_response.expect(:cinema_list, cinema_list_json)
217
- end
60
+ def test_name_replaces_dash_separator
61
+ assert_equal 'Example: Riverside', CineworldUk::Cinema.new(999).name
62
+ end
218
63
 
219
- describe 'Brighton (14)' do
220
- let(:id) { 14 }
64
+ def test_locality
65
+ assert_equal 'Brighton', CineworldUk::Cinema.new(14).locality
66
+ assert_equal 'London', CineworldUk::Cinema.new(103).locality
67
+ end
221
68
 
222
- it 'returns the first line of the Address' do
223
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
224
- _(subject).must_equal 'Brighton Marina Village'
225
- end
226
- end
227
- end
69
+ def test_postal_code
70
+ assert_equal 'BN2 0ZZ', CineworldUk::Cinema.new(14).postal_code
228
71
  end
229
72
 
230
- describe '#url' do
231
- subject { described_class.new(id).url }
73
+ def test_region
74
+ assert_equal 'East Sussex', CineworldUk::Cinema.new(14).region
75
+ end
232
76
 
233
- before do
234
- api_response.expect(:cinema_list, cinema_list_json)
235
- end
77
+ def test_region_defaults_to_london_for_london_cinemas
78
+ assert_equal 'London', CineworldUk::Cinema.new(103).region
79
+ end
236
80
 
237
- describe 'Brighton (14)' do
238
- let(:id) { 14 }
81
+ def test_street_address
82
+ assert_equal '1 Example Marina', CineworldUk::Cinema.new(14).street_address
83
+ end
239
84
 
240
- it 'returns the url' do
241
- CineworldUk::Internal::ApiResponse.stub :new, api_response do
242
- _(subject).must_equal(
243
- "http://www.cineworld.co.uk/cinemas/#{id}/information"
244
- )
245
- end
246
- end
247
- end
85
+ def test_url
86
+ assert_equal 'http://www.cineworld.co.uk/cinemas/14/information',
87
+ CineworldUk::Cinema.new(14).url
248
88
  end
249
89
  end
@@ -2,129 +2,50 @@
2
2
 
3
3
  require_relative "../../../test_helper"
4
4
 
5
- # This block groups independent Minitest cases, not application logic.
6
- describe "Vista retrieval" do # rubocop:disable Metrics/BlockLength
7
- before do
8
- CineworldUk.token = "test-guest-token"
9
- [CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
10
- klass.instance_variables.each do |name|
11
- klass.remove_instance_variable(name)
12
- end
13
- end
14
- fixture = "../../../fixtures/api/vista-showtimes-brighton.json"
15
- @capture = JSON.parse(File.read(File.expand_path(fixture, __dir__)))
16
- @base = "https://digital-api.cineworld.co.uk/ocapi/v1/"
17
- sites = @capture.fetch("relatedData").fetch("sites")
18
- stub_request(:get, @base + "sites").with(
19
- headers: {
20
- "Authorization" => "Bearer test-guest-token",
21
- "Connect-Region-Code" => "GB"
22
- }
23
- ).to_return(body: JSON.generate("sites" => sites))
24
- end
5
+ class CineworldUk::Internal::ApiResponseTest < Minitest::Test
6
+ include Support::VistaFixtures
25
7
 
26
- after do
27
- CineworldUk.token = nil
28
- [CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
29
- klass.instance_variables.each do |name|
30
- klass.remove_instance_variable(name)
31
- end
32
- end
8
+ def setup
9
+ setup_vista
33
10
  end
34
11
 
35
- def stub_day(day, body)
36
- stub_request(:get, @base + "showtimes/by-business-date/#{day}").with(
37
- query: { "siteIds" => "014" },
38
- headers: { "Authorization" => "Bearer test-guest-token",
39
- "Connect-Region-Code" => "GB" }
40
- ).to_return(body: JSON.generate(body))
12
+ def teardown
13
+ teardown_vista
14
+ super
41
15
  end
42
16
 
43
- it "retrieves cinemas and advertised session times from Vista" do
44
- stub_day("2026-10-14", @capture)
45
- cinema = CineworldUk::Cinema.all.first
46
- _(cinema.id).must_equal 14
47
- _(cinema.name).must_equal "Brighton"
48
- _(cinema.postal_code).must_equal "BN2 5UF"
49
- _(cinema.street_address).must_equal "Brighton Marina Village"
50
- assert_requested :get, @base + "sites", times: 1
51
- performances = CineworldUk::Performance.at(
52
- 14, from: Date.new(2026, 10, 14), to: Date.new(2026, 10, 14)
53
- )
54
- _(performances.size).must_equal 17
55
- first = performances.first
56
- _(first.film_name).must_equal "Other Mommy"
57
- _(first.starting_at).must_equal Time.utc(2026, 10, 14, 14, 40)
58
- _(first.booking_url).must_equal(
59
- "https://web.cineworld.co.uk/order/showtimes/014-165507/seats"
60
- )
61
- _(first.variant).must_include "audio_described"
62
- _(first.dimension).must_equal "2d"
63
- assert_requested :get, @base + "sites", times: 1
64
- assert_requested :get, @base + "showtimes/by-business-date/2026-10-14",
65
- query: { "siteIds" => "014" }, times: 1
66
- end
17
+ def test_rejects_missing_token
18
+ CineworldUk.token = nil
67
19
 
68
- it "keeps format session-specific and includes the last requested day" do
69
- @capture["showtimes"] =
70
- [@capture["showtimes"].first,
71
- Marshal.load(Marshal.dump(@capture["showtimes"].first))]
72
- @capture["showtimes"][1]["id"] = "014-second"
73
- @capture["showtimes"][1]["requires3dGlasses"] = true
74
- @capture["showtimes"][1]["schedule"]["startsAt"] =
75
- "2026-10-25T16:40:00+00:00"
76
- stub_day("2026-10-24",
77
- "showtimes" => [],
78
- "relatedData" => { "films" => [], "attributes" => [],
79
- "sites" => [] })
80
- stub_day("2026-10-25", @capture)
81
- results = CineworldUk::Performance.at("014", from: Date.new(2026, 10, 24),
82
- to: Date.new(2026, 10, 25))
83
- _(results.map(&:dimension)).must_equal %w[2d 3d]
84
- _(results.last.starting_at).must_equal Time.utc(2026, 10, 25, 16, 40)
85
- _(results.first.cinema_name).must_equal "Brighton"
86
- assert_not_requested :get, @base + "sites"
20
+ assert_raises(CineworldUk::AuthenticationError) { CineworldUk::Cinema.all }
87
21
  end
88
22
 
89
- it "rejects missing and rejected tokens without exposing credentials" do
90
- CineworldUk.token = nil
91
- assert_raises(CineworldUk::AuthenticationError) { CineworldUk::Cinema.all }
23
+ def test_rejects_token_without_exposing_it
92
24
  CineworldUk.token = "expired-secret"
93
- stub_request(:get, @base + "sites").to_return(status: 401,
94
- body: "expired-secret")
95
- error = assert_raises(CineworldUk::AuthenticationError) do
96
- CineworldUk::Cinema.all
97
- end
25
+ stub_request(:get, "#{BASE_URL}sites").to_return(status: 401, body: "expired-secret")
26
+
27
+ error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk::Cinema.all }
28
+
98
29
  refute_includes error.message, "expired-secret"
99
- CineworldUk.token = "replacement"
100
- stub_request(:get, @base + "sites").with(
101
- headers: { "Authorization" => "Bearer replacement" }
102
- ).to_return(body: '{"sites":[]}')
103
- _(CineworldUk::Cinema.all).must_equal []
104
30
  end
105
31
 
106
- it "defaults to fourteen days and rejects reversed ranges" do
107
- day = Date.new(2026, 10, 14)
108
- (day..day + 13).each do |date|
109
- stub_day(date.iso8601,
110
- "showtimes" => [],
111
- "relatedData" => { "films" => [], "attributes" => [],
112
- "sites" => [] })
113
- end
114
- _(CineworldUk::Performance.at(14, from: day)).must_equal []
115
- assert_requested :get, %r{showtimes/by-business-date/}, times: 14
116
- assert_raises(ArgumentError) do
117
- CineworldUk::Performance.at(14, from: day, to: day - 1)
118
- end
119
- assert_not_requested :get, @base + "sites"
32
+ def test_uses_replacement_token
33
+ CineworldUk.token = "replacement"
34
+ stub_request(:get, "#{BASE_URL}sites")
35
+ .with(headers: { "Authorization" => "Bearer replacement" })
36
+ .to_return(body: '{"sites":[]}')
37
+
38
+ assert_equal [], CineworldUk::Cinema.all
120
39
  end
121
40
 
122
- it "does not follow redirects or hide forbidden responses" do
41
+ def test_does_not_follow_redirects_or_hide_forbidden_responses
123
42
  [302, 403].each do |status|
124
- stub_request(:get, @base + "sites").to_return(
125
- status: status, headers: { "Location" => "https://example.com/" }
126
- )
43
+ reset_caches
44
+ stub_request(:get, "#{BASE_URL}sites")
45
+ .to_return(status: status, headers: { "Location" => "https://example.com/" })
46
+
127
47
  error = assert_raises(RuntimeError) { CineworldUk::Cinema.all }
48
+
128
49
  assert_includes error.message, status.to_s
129
50
  end
130
51
  end
@@ -0,0 +1,14 @@
1
+ require_relative '../../../test_helper'
2
+
3
+ class FixtureShapeTest < Minitest::Test
4
+ include Support::VistaFixtures
5
+ include ApiShapes
6
+
7
+ def test_sites_fixture_matches_shape
8
+ assert_sites_shape(JSON.parse(sites_json))
9
+ end
10
+
11
+ def test_showtimes_fixture_matches_shape
12
+ assert_showtimes_shape(JSON.parse(showtimes_json))
13
+ end
14
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "base64"
5
+ require "tmpdir"
6
+
7
+ class CineworldUk::Internal::GuestTokenTest < Minitest::Test
8
+ def setup
9
+ @directory = Dir.mktmpdir("cineworld token ")
10
+ @curl = File.join(@directory, "curl-impersonate")
11
+ CineworldUk.token = "existing-token"
12
+ end
13
+
14
+ def teardown
15
+ FileUtils.remove_entry(@directory)
16
+ CineworldUk.token = nil
17
+ end
18
+
19
+ def test_sets_and_returns_token_from_200_or_404_page
20
+ html, token = token_page(Time.now.to_i + 3600)
21
+
22
+ [200, 404].each do |status|
23
+ serve_token_page(html, status: status)
24
+
25
+ assert_equal token, CineworldUk.acquire_token!(curl: @curl)
26
+ assert_equal token, CineworldUk.token
27
+ end
28
+ end
29
+
30
+ def test_rejects_expired_tokens_including_boundary_and_keeps_current_token
31
+ now = Time.at(1_800_000_000)
32
+ Time.stub(:now, now) do
33
+ [-1, 0, 1].each do |offset|
34
+ html, token = token_page(now.to_i + offset)
35
+ serve_token_page(html)
36
+ if offset.positive?
37
+ assert_equal token, CineworldUk.acquire_token!(curl: @curl)
38
+ else
39
+ assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
40
+ assert_equal "existing-token", CineworldUk.token
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_reports_bad_pages_without_exposing_the_response
47
+ html, token = token_page(Time.now.to_i + 3600)
48
+ invalid_payload = Base64.urlsafe_encode64("broken-secret", padding: false)
49
+ [[html, 403], [html, 302], ["Cloudflare block", 200],
50
+ ["<script id='__NEXT_DATA__'>{</script>", 200],
51
+ ["<script id='__NEXT_DATA__'>{}</script>", 200],
52
+ [html.sub(token, "broken-secret"), 200],
53
+ [html.sub(token, "header.#{invalid_payload}.signature"), 200]].each do |body, status|
54
+ serve_token_page(body, status: status)
55
+
56
+ error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
57
+
58
+ refute_includes error.full_message, token
59
+ refute_includes error.full_message, "broken-secret"
60
+ assert_equal "existing-token", CineworldUk.token
61
+ end
62
+ end
63
+
64
+ def test_reports_missing_executable_or_curl_failure
65
+ error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
66
+ assert_includes error.message, "Install curl-impersonate"
67
+
68
+ serve_token_page("", status: 0, exit_code: 28)
69
+ error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
70
+ assert_includes error.message, "curl exit 28"
71
+ end
72
+
73
+ private
74
+
75
+ def serve_token_page(html, status: 200, exit_code: 0)
76
+ output = "#{html}\n#{status}"
77
+ File.write(@curl, "#!#{RbConfig.ruby}\nSTDOUT.write(#{output.dump})\nexit #{exit_code}\n")
78
+ File.chmod(0o700, @curl)
79
+ end
80
+
81
+ def token_page(expires)
82
+ payload = Base64.urlsafe_encode64(JSON.generate("exp" => expires), padding: false)
83
+ token = "header.#{payload}.signature"
84
+ data = { "props" => { "pageProps" => { "environment" => { "gasToken" => token } } } }
85
+ ["<script type='application/json' id='__NEXT_DATA__'>#{JSON.generate(data)}</script>", token]
86
+ end
87
+ end