shopify_app 22.5.2 → 23.0.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yaml +6 -0
  3. data/.github/workflows/build.yml +5 -5
  4. data/.github/workflows/close-waiting-for-response-issues.yml +1 -1
  5. data/.github/workflows/release.yml +2 -2
  6. data/.github/workflows/remove-labels-on-activity.yml +2 -3
  7. data/.github/workflows/rubocop.yml +2 -2
  8. data/CHANGELOG.md +22 -0
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +181 -122
  11. data/README.md +105 -25
  12. data/Rakefile +13 -0
  13. data/app/controllers/shopify_app/callback_controller.rb +2 -40
  14. data/app/controllers/shopify_app/extension_verification_controller.rb +1 -1
  15. data/app/jobs/shopify_app/script_tags_manager_job.rb +16 -0
  16. data/docs/Releasing.md +113 -19
  17. data/docs/Upgrading.md +72 -0
  18. data/docs/shopify_app/content-security-policy.md +50 -3
  19. data/docs/shopify_app/controller-concerns.md +20 -0
  20. data/docs/shopify_app/script-tags.md +52 -0
  21. data/docs/shopify_app/sessions.md +149 -22
  22. data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +2 -2
  23. data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_data_request_job.rb.tt +2 -2
  24. data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_redact_job.rb.tt +2 -2
  25. data/lib/generators/shopify_app/add_privacy_jobs/templates/shop_redact_job.rb.tt +2 -2
  26. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +2 -2
  27. data/lib/generators/shopify_app/install/install_generator.rb +1 -1
  28. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +18 -0
  29. data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_token_expiry_columns.erb +7 -0
  30. data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -1
  31. data/lib/generators/shopify_app/user_model/templates/user.rb +1 -1
  32. data/lib/shopify_app/auth/post_authenticate_tasks.rb +8 -0
  33. data/lib/shopify_app/auth/token_exchange.rb +7 -0
  34. data/lib/shopify_app/configuration.rb +5 -5
  35. data/lib/shopify_app/controller_concerns/login_protection.rb +12 -8
  36. data/lib/shopify_app/engine.rb +2 -5
  37. data/lib/shopify_app/errors.rb +2 -0
  38. data/lib/shopify_app/managers/script_tags_manager.rb +348 -0
  39. data/lib/shopify_app/session/shop_session_storage.rb +84 -2
  40. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +6 -0
  41. data/lib/shopify_app/session/user_session_storage.rb +21 -2
  42. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +6 -0
  43. data/lib/shopify_app/utils.rb +1 -1
  44. data/lib/shopify_app/version.rb +1 -1
  45. data/lib/shopify_app.rb +12 -7
  46. data/package.json +1 -1
  47. data/shopify_app.gemspec +9 -10
  48. data/translation.yml +1 -0
  49. data/yarn.lock +7 -9
  50. metadata +63 -46
  51. data/lib/shopify_app/middleware/jwt_middleware.rb +0 -48
  52. data/lib/shopify_app/session/jwt.rb +0 -73
  53. /data/{lib/shopify_app/jobs → app/jobs/shopify_app}/webhooks_manager_job.rb +0 -0
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ class ScriptTagsManager
5
+ def self.queue(shop_domain, shop_token, script_tags)
6
+ ShopifyApp::ScriptTagsManagerJob.perform_later(
7
+ shop_domain: shop_domain,
8
+ shop_token: shop_token,
9
+ # Procs cannot be serialized so we interpolate now, if necessary
10
+ script_tags: build_src(script_tags, shop_domain),
11
+ )
12
+ end
13
+
14
+ def self.build_src(script_tags, domain)
15
+ script_tags.map do |tag|
16
+ next tag unless tag[:src].respond_to?(:call)
17
+
18
+ tag = tag.dup
19
+ tag[:src] = tag[:src].call(domain)
20
+ tag
21
+ end
22
+ end
23
+
24
+ attr_reader :required_script_tags, :shop_domain
25
+
26
+ def initialize(script_tags, shop_domain)
27
+ @required_script_tags = script_tags
28
+ @shop_domain = shop_domain
29
+ @session = nil
30
+ end
31
+
32
+ def recreate_script_tags!(session:)
33
+ destroy_script_tags(session: session)
34
+ create_script_tags(session: session)
35
+ end
36
+
37
+ def create_script_tags(session:)
38
+ @session = session
39
+ return unless required_script_tags.present?
40
+
41
+ template_types_to_check = required_script_tags.flat_map { |tag| tag[:template_types] }.compact.uniq
42
+
43
+ if template_types_to_check.any?
44
+ active_theme = fetch_active_theme
45
+ unless active_theme
46
+ ShopifyApp::Logger.debug("Failed to fetch active theme. Skipping script tag creation.")
47
+ return
48
+ end
49
+
50
+ if all_templates_support_app_blocks?(active_theme["id"], template_types_to_check)
51
+ ShopifyApp::Logger.info(
52
+ "Theme supports app blocks for templates: #{template_types_to_check.join(", ")}. " \
53
+ "Skipping script tag creation.",
54
+ )
55
+ return
56
+ end
57
+ end
58
+
59
+ expanded_script_tags.each do |script_tag|
60
+ create_script_tag(script_tag) unless script_tag_exists?(script_tag[:src])
61
+ end
62
+ end
63
+
64
+ def destroy_script_tags(session:)
65
+ @session = session
66
+ script_tags = expanded_script_tags
67
+ fetch_all_script_tags.each do |tag|
68
+ delete_script_tag(tag) if required_script_tag?(script_tags, tag)
69
+ end
70
+
71
+ @current_script_tags = nil
72
+ end
73
+
74
+ private
75
+
76
+ FILES_QUERY = <<~QUERY
77
+ query getFiles($themeId: ID!, $filenames: [String!]!) {
78
+ theme(id: $themeId) {
79
+ files(filenames: $filenames) {
80
+ nodes {
81
+ filename
82
+ body {
83
+ ... on OnlineStoreThemeFileBodyText {
84
+ content
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ QUERY
92
+
93
+ ACTIVE_THEME_QUERY = <<~QUERY
94
+ {
95
+ themes(first: 1, roles: [MAIN]) {
96
+ nodes {
97
+ id
98
+ name
99
+ }
100
+ }
101
+ }
102
+ QUERY
103
+
104
+ SCRIPT_TAG_CREATE_MUTATION = <<~QUERY
105
+ mutation ScriptTagCreate($input: ScriptTagInput!) {
106
+ scriptTagCreate(input: $input) {
107
+ scriptTag {
108
+ id
109
+ src
110
+ displayScope
111
+ cache
112
+ }
113
+ userErrors {
114
+ field
115
+ message
116
+ }
117
+ }
118
+ }
119
+ QUERY
120
+
121
+ SCRIPT_TAG_DELETE_MUTATION = <<~QUERY
122
+ mutation scriptTagDelete($id: ID!) {
123
+ scriptTagDelete(id: $id) {
124
+ deletedScriptTagId
125
+ userErrors {
126
+ field
127
+ message
128
+ }
129
+ }
130
+ }
131
+ QUERY
132
+
133
+ SCRIPT_TAGS_QUERY = <<~QUERY
134
+ {
135
+ scriptTags(first: 250) {
136
+ edges {
137
+ node {
138
+ id
139
+ src
140
+ displayScope
141
+ cache
142
+ }
143
+ }
144
+ }
145
+ }
146
+ QUERY
147
+
148
+ def fetch_active_theme
149
+ client = graphql_client
150
+
151
+ response = client.query(query: ACTIVE_THEME_QUERY)
152
+
153
+ if response.body["errors"].present?
154
+ error_message = response.body["errors"].map { |e| e["message"] }.join(", ")
155
+ raise ShopifyAPI::Errors::InvalidGraphqlRequestError, error_message
156
+ end
157
+
158
+ themes = response.body["data"]["themes"]["nodes"]
159
+ return if themes.empty?
160
+
161
+ themes.first
162
+ rescue => e
163
+ ShopifyApp::Logger.warn("Failed to fetch active theme: #{e.message}")
164
+ nil
165
+ end
166
+
167
+ def all_templates_support_app_blocks?(theme_id, template_types)
168
+ client = graphql_client
169
+
170
+ template_filenames = template_types.map { |type| "templates/#{type}.json" }
171
+ json_templates = fetch_json_templates(client, theme_id, template_filenames)
172
+
173
+ return false if json_templates.length != template_types.length
174
+
175
+ main_sections = extract_main_sections(json_templates)
176
+
177
+ return false if main_sections.length != template_types.length
178
+
179
+ all_sections_support_app_blocks?(client, theme_id, main_sections)
180
+ rescue => e
181
+ ShopifyApp::Logger.error("Error checking template support: #{e.message}")
182
+ false
183
+ end
184
+
185
+ def fetch_json_templates(client, theme_id, template_filenames)
186
+ files_variables = {
187
+ themeId: theme_id,
188
+ filenames: template_filenames,
189
+ }
190
+
191
+ files_response = client.query(query: FILES_QUERY, variables: files_variables)
192
+
193
+ if files_response.body["errors"].present?
194
+ error_message = files_response.body["errors"].map { |e| e["message"] }.join(", ")
195
+ raise ShopifyAPI::Errors::InvalidGraphqlRequestError, error_message
196
+ end
197
+
198
+ files_response.body["data"]["theme"]["files"]["nodes"]
199
+ end
200
+
201
+ def extract_main_sections(json_templates)
202
+ main_sections = []
203
+
204
+ json_templates.each do |template|
205
+ template_content = template["body"]["content"]
206
+ template_data = JSON.parse(template_content)
207
+
208
+ main_section = nil
209
+ template_data["sections"].each do |id, section|
210
+ if id == "main" || section["type"].to_s.start_with?("main-")
211
+ main_section = "sections/#{section["type"]}.liquid"
212
+ break
213
+ end
214
+ end
215
+
216
+ main_sections << main_section if main_section
217
+ rescue => e
218
+ ShopifyApp::Logger.error("Error extracting main section from template #{template["filename"]}: #{e.message}")
219
+ end
220
+
221
+ main_sections
222
+ end
223
+
224
+ def all_sections_support_app_blocks?(client, theme_id, section_filenames)
225
+ return false if section_filenames.empty?
226
+
227
+ section_variables = {
228
+ themeId: theme_id,
229
+ filenames: section_filenames,
230
+ }
231
+
232
+ section_response = client.query(query: FILES_QUERY, variables: section_variables)
233
+
234
+ if section_response.body["errors"].present?
235
+ error_message = section_response.body["errors"].map { |e| e["message"] }.join(", ")
236
+ raise ShopifyAPI::Errors::InvalidGraphqlRequestError, error_message
237
+ end
238
+
239
+ section_files = section_response.body["data"]["theme"]["files"]["nodes"]
240
+
241
+ return false if section_files.length != section_filenames.length
242
+
243
+ # Check if all sections support app blocks
244
+ section_files.all? do |file|
245
+ section_content = file["body"]["content"]
246
+ schema_match = section_content.match(/\{\%\s+schema\s+\%\}([\s\S]*?)\{\%\s+endschema\s+\%\}/m)
247
+ next false unless schema_match
248
+
249
+ schema = JSON.parse(schema_match[1])
250
+ schema["blocks"]&.any? { |block| block["type"] == "@app" } || false
251
+ end
252
+ rescue => e
253
+ ShopifyApp::Logger.error("Error checking section support: #{e.message}")
254
+ false
255
+ end
256
+
257
+ def expanded_script_tags
258
+ self.class.build_src(required_script_tags, shop_domain)
259
+ end
260
+
261
+ def required_script_tag?(script_tags, tag)
262
+ script_tags.map { |w| w[:src] }.include?(tag["src"])
263
+ end
264
+
265
+ def create_script_tag(attributes)
266
+ client = graphql_client
267
+
268
+ variables = {
269
+ input: {
270
+ src: attributes[:src],
271
+ displayScope: "ONLINE_STORE",
272
+ cache: attributes[:cache] || false,
273
+ },
274
+ }
275
+
276
+ response = client.query(query: SCRIPT_TAG_CREATE_MUTATION, variables: variables)
277
+
278
+ if response.body["errors"].present?
279
+ error_messages = response.body["errors"].map { |e| e["message"] }.join(", ")
280
+ raise ::ShopifyApp::CreationFailed, "ScriptTag creation failed: #{error_messages}"
281
+ end
282
+
283
+ if response.body["data"]["scriptTagCreate"]["userErrors"].any?
284
+ errors = response.body["data"]["scriptTagCreate"]["userErrors"]
285
+ error_messages = errors.map { |e| "#{e["field"]}: #{e["message"]}" }.join(", ")
286
+ raise ::ShopifyApp::CreationFailed, "ScriptTag creation failed: #{error_messages}"
287
+ end
288
+
289
+ response.body["data"]["scriptTagCreate"]["scriptTag"]
290
+ rescue ShopifyAPI::Errors::HttpResponseError => e
291
+ raise ::ShopifyApp::CreationFailed, e.message
292
+ end
293
+
294
+ def delete_script_tag(tag)
295
+ client = graphql_client
296
+
297
+ variables = { id: tag["id"] }
298
+
299
+ response = client.query(query: SCRIPT_TAG_DELETE_MUTATION, variables: variables)
300
+
301
+ if response.body["errors"].present?
302
+ error_messages = response.body["errors"].map { |e| e["message"] }.join(", ")
303
+ ShopifyApp::Logger.error("Failed to delete script tag: #{error_messages}")
304
+ return
305
+ end
306
+
307
+ if response.body["data"]["scriptTagDelete"]["userErrors"].any?
308
+ errors = response.body["data"]["scriptTagDelete"]["userErrors"]
309
+ error_messages = errors.map { |e| "#{e["field"]}: #{e["message"]}" }.join(", ")
310
+ ShopifyApp::Logger.error("Failed to delete script tag: #{error_messages}")
311
+ return
312
+ end
313
+
314
+ response.body["data"]["scriptTagDelete"]["deletedScriptTagId"]
315
+ rescue ShopifyAPI::Errors::HttpResponseError => e
316
+ ShopifyApp::Logger.error("Failed to delete script tag: #{e.message}")
317
+ end
318
+
319
+ def script_tag_exists?(src)
320
+ current_script_tags[src]
321
+ end
322
+
323
+ def current_script_tags
324
+ @current_script_tags ||= fetch_all_script_tags.index_by { |tag| tag["src"] }
325
+ end
326
+
327
+ def fetch_all_script_tags
328
+ client = graphql_client
329
+
330
+ response = client.query(query: SCRIPT_TAGS_QUERY)
331
+
332
+ if response.body["errors"].present?
333
+ error_messages = response.body["errors"].map { |e| e["message"] }.join(", ")
334
+ ShopifyApp::Logger.warn("GraphQL error fetching script tags: #{error_messages}")
335
+ return []
336
+ end
337
+
338
+ response.body["data"]["scriptTags"]["edges"].map { |edge| edge["node"] }
339
+ rescue => e
340
+ ShopifyApp::Logger.warn("Error fetching script tags: #{e.message}")
341
+ []
342
+ end
343
+
344
+ def graphql_client
345
+ ShopifyAPI::Clients::Graphql::Admin.new(session: @session)
346
+ end
347
+ end
348
+ end
@@ -9,10 +9,46 @@ module ShopifyApp
9
9
  validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
10
10
  end
11
11
 
12
+ def with_shopify_session(auto_refresh: true, &block)
13
+ refresh_token_if_expired! if auto_refresh
14
+ super(&block)
15
+ end
16
+
17
+ def refresh_token_if_expired!
18
+ return unless should_refresh?
19
+ raise RefreshTokenExpiredError if refresh_token_expired?
20
+
21
+ # Acquire row lock to prevent concurrent refreshes
22
+ with_lock do
23
+ reload
24
+ # Check again after lock - token might have been refreshed by another process
25
+ return unless should_refresh?
26
+
27
+ perform_token_refresh!
28
+ end
29
+ end
30
+
12
31
  class_methods do
13
32
  def store(auth_session, *_args)
14
33
  shop = find_or_initialize_by(shopify_domain: auth_session.shop)
15
34
  shop.shopify_token = auth_session.access_token
35
+
36
+ if shop.has_attribute?(:access_scopes)
37
+ shop.access_scopes = auth_session.scope.to_s
38
+ end
39
+
40
+ if shop.has_attribute?(:expires_at)
41
+ shop.expires_at = auth_session.expires
42
+ end
43
+
44
+ if shop.has_attribute?(:refresh_token)
45
+ shop.refresh_token = auth_session.refresh_token
46
+ end
47
+
48
+ if shop.has_attribute?(:refresh_token_expires_at)
49
+ shop.refresh_token_expires_at = auth_session.refresh_token_expires
50
+ end
51
+
16
52
  shop.save!
17
53
  shop.id
18
54
  end
@@ -36,11 +72,57 @@ module ShopifyApp
36
72
  def construct_session(shop)
37
73
  return unless shop
38
74
 
39
- ShopifyAPI::Auth::Session.new(
75
+ session_attrs = {
40
76
  shop: shop.shopify_domain,
41
77
  access_token: shop.shopify_token,
42
- )
78
+ }
79
+
80
+ if shop.has_attribute?(:access_scopes)
81
+ session_attrs[:scope] = shop.access_scopes
82
+ end
83
+
84
+ if shop.has_attribute?(:expires_at)
85
+ session_attrs[:expires] = shop.expires_at
86
+ end
87
+
88
+ if shop.has_attribute?(:refresh_token)
89
+ session_attrs[:refresh_token] = shop.refresh_token
90
+ end
91
+
92
+ if shop.has_attribute?(:refresh_token_expires_at)
93
+ session_attrs[:refresh_token_expires] = shop.refresh_token_expires_at
94
+ end
95
+
96
+ ShopifyAPI::Auth::Session.new(**session_attrs)
43
97
  end
44
98
  end
99
+
100
+ def perform_token_refresh!
101
+ new_session = ShopifyAPI::Auth::RefreshToken.refresh_access_token(
102
+ shop: shopify_domain,
103
+ refresh_token: refresh_token,
104
+ )
105
+
106
+ update!(
107
+ shopify_token: new_session.access_token,
108
+ expires_at: new_session.expires,
109
+ refresh_token: new_session.refresh_token,
110
+ refresh_token_expires_at: new_session.refresh_token_expires,
111
+ )
112
+ end
113
+
114
+ def should_refresh?
115
+ return false unless has_attribute?(:expires_at) && expires_at.present?
116
+ return false unless has_attribute?(:refresh_token) && refresh_token.present?
117
+ return false unless has_attribute?(:refresh_token_expires_at) && refresh_token_expires_at.present?
118
+
119
+ expires_at <= Time.now
120
+ end
121
+
122
+ def refresh_token_expired?
123
+ return false unless has_attribute?(:refresh_token_expires_at) && refresh_token_expires_at.present?
124
+
125
+ refresh_token_expires_at <= Time.now
126
+ end
45
127
  end
46
128
  end
@@ -6,6 +6,12 @@ module ShopifyApp
6
6
  include ::ShopifyApp::SessionStorage
7
7
 
8
8
  included do
9
+ ShopifyApp::Logger.deprecated(
10
+ "ShopSessionStorageWithScopes is deprecated and will be removed in v24.0.0. " \
11
+ "Use ShopSessionStorage instead, which now handles access_scopes, expires_at, " \
12
+ "refresh_token, and refresh_token_expires_at automatically.",
13
+ "24.0.0",
14
+ )
9
15
  validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
10
16
  end
11
17
 
@@ -14,6 +14,15 @@ module ShopifyApp
14
14
  user = find_or_initialize_by(shopify_user_id: user.id)
15
15
  user.shopify_token = auth_session.access_token
16
16
  user.shopify_domain = auth_session.shop
17
+
18
+ if user.has_attribute?(:access_scopes)
19
+ user.access_scopes = auth_session.scope.to_s
20
+ end
21
+
22
+ if user.has_attribute?(:expires_at)
23
+ user.expires_at = auth_session.expires
24
+ end
25
+
17
26
  user.save!
18
27
  user.id
19
28
  end
@@ -48,11 +57,21 @@ module ShopifyApp
48
57
  collaborator: false,
49
58
  )
50
59
 
51
- ShopifyAPI::Auth::Session.new(
60
+ session_attrs = {
52
61
  shop: user.shopify_domain,
53
62
  access_token: user.shopify_token,
54
63
  associated_user: associated_user,
55
- )
64
+ }
65
+
66
+ if user.has_attribute?(:access_scopes)
67
+ session_attrs[:scope] = user.access_scopes
68
+ end
69
+
70
+ if user.has_attribute?(:expires_at)
71
+ session_attrs[:expires] = user.expires_at
72
+ end
73
+
74
+ ShopifyAPI::Auth::Session.new(**session_attrs)
56
75
  end
57
76
  end
58
77
  end
@@ -6,6 +6,12 @@ module ShopifyApp
6
6
  include ::ShopifyApp::SessionStorage
7
7
 
8
8
  included do
9
+ ShopifyApp::Logger.deprecated(
10
+ "UserSessionStorageWithScopes is deprecated and will be removed in v24.0.0. " \
11
+ "Use UserSessionStorage instead, which now handles access_scopes and expires_at automatically.",
12
+ "24.0.0",
13
+ )
14
+
9
15
  validates :shopify_domain, presence: true
10
16
  end
11
17
 
@@ -13,7 +13,7 @@ module ShopifyApp
13
13
 
14
14
  def sanitize_shop_domain(shop_domain)
15
15
  uri = uri_from_shop_domain(shop_domain)
16
- return nil if uri.nil? || uri.host.nil?
16
+ return if uri.nil? || uri.host.nil?
17
17
 
18
18
  trusted_domains.each do |trusted_domain|
19
19
  no_shop_name_in_subdomain = uri.host == trusted_domain
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShopifyApp
4
- VERSION = "22.5.2"
4
+ VERSION = "23.0.0"
5
5
  end
data/lib/shopify_app.rb CHANGED
@@ -26,6 +26,17 @@ module ShopifyApp
26
26
  !configuration.disable_webpacker
27
27
  end
28
28
 
29
+ def self.add_csp_directives(policy)
30
+ # Get current script-src directives
31
+ current_script_src = policy.directives["script-src"] || []
32
+
33
+ # Add App Bridge script source if not already present
34
+ app_bridge_url = "https://cdn.shopify.com/shopifycloud/app-bridge.js"
35
+ unless current_script_src.include?(app_bridge_url)
36
+ policy.script_src(*current_script_src, app_bridge_url)
37
+ end
38
+ end
39
+
29
40
  # config
30
41
  require "shopify_app/configuration"
31
42
 
@@ -62,20 +73,14 @@ module ShopifyApp
62
73
  require "shopify_app/auth/post_authenticate_tasks"
63
74
  require "shopify_app/auth/token_exchange"
64
75
 
65
- # jobs
66
- require "shopify_app/jobs/webhooks_manager_job"
67
-
68
76
  # managers
69
77
  require "shopify_app/managers/webhooks_manager"
70
-
71
- # middleware
72
- require "shopify_app/middleware/jwt_middleware"
78
+ require "shopify_app/managers/script_tags_manager"
73
79
 
74
80
  # session
75
81
  require "shopify_app/session/in_memory_session_store"
76
82
  require "shopify_app/session/in_memory_shop_session_store"
77
83
  require "shopify_app/session/in_memory_user_session_store"
78
- require "shopify_app/session/jwt"
79
84
  require "shopify_app/session/null_user_session_store"
80
85
  require "shopify_app/session/session_repository"
81
86
  require "shopify_app/session/session_storage"
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shopify_app",
3
- "version": "22.5.2",
3
+ "version": "23.0.0",
4
4
  "repository": "git@github.com:Shopify/shopify_app.git",
5
5
  "author": "Shopify",
6
6
  "license": "MIT",
data/shopify_app.gemspec CHANGED
@@ -10,29 +10,28 @@ Gem::Specification.new do |s|
10
10
  s.author = "Shopify"
11
11
  s.summary = "This gem is used to get quickly started with the Shopify API"
12
12
 
13
- s.required_ruby_version = ">= 3.0"
13
+ s.required_ruby_version = ">= 3.2"
14
14
 
15
15
  s.metadata["allowed_push_host"] = "https://rubygems.org"
16
16
 
17
- s.add_runtime_dependency("activeresource") # TODO: Remove this once all active resource dependencies are removed
18
17
  s.add_runtime_dependency("addressable", "~> 2.7")
19
- s.add_runtime_dependency("rails", "> 5.2.1")
18
+ s.add_runtime_dependency("rails", ">= 7.1", "< 9")
20
19
  s.add_runtime_dependency("redirect_safely", "~> 1.0")
21
- s.add_runtime_dependency("shopify_api", ">= 14.7.0", "< 15.0")
22
- s.add_runtime_dependency("sprockets-rails", ">= 2.0.0")
23
- # Deprecated: move to development dependencies when releasing v23
24
- s.add_runtime_dependency("jwt", ">= 2.2.3")
25
-
20
+ s.add_runtime_dependency("shopify_api", "~> 16.0")
21
+ s.add_runtime_dependency("sprockets-rails")
26
22
  s.add_development_dependency("byebug")
23
+ s.add_development_dependency("csv")
24
+ s.add_development_dependency("jwt", ">= 2.2.3")
27
25
  s.add_development_dependency("minitest")
28
- s.add_development_dependency("mocha")
26
+ s.add_development_dependency("mocha", ">= 2.1.0")
27
+ s.add_development_dependency("mutex_m")
29
28
  s.add_development_dependency("pry")
30
29
  s.add_development_dependency("pry-nav")
31
30
  s.add_development_dependency("pry-stack_explorer")
32
31
  s.add_development_dependency("rake")
33
32
  s.add_development_dependency("rb-readline")
34
33
  s.add_development_dependency("ruby-lsp")
35
- s.add_development_dependency("sqlite3", "~> 1.4")
34
+ s.add_development_dependency("sqlite3")
36
35
  s.add_development_dependency("webmock")
37
36
 
38
37
  s.files = %x(git ls-files).split("\n").reject { |f| f.match(%r{^(test|example)/}) }
data/translation.yml CHANGED
@@ -3,6 +3,7 @@ target_languages: [cs, da, de, es, fi, fr, it, ja, ko, nb, nl, pl, pt-BR, pt-PT,
3
3
  async_pr_mode: per_pr
4
4
  components:
5
5
  - name: 'merchant'
6
+ audience: merchant
6
7
  paths:
7
8
  - config/locales/{{language}}.yml
8
9
  - config/locales/**/{{language}}.yml
data/yarn.lock CHANGED
@@ -2644,9 +2644,9 @@ mime@^2.5.2:
2644
2644
  integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
2645
2645
 
2646
2646
  min-document@^2.19.0:
2647
- version "2.19.0"
2648
- resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
2649
- integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==
2647
+ version "2.19.2"
2648
+ resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.2.tgz#f95db44639eaae3ac8ea85ae6809ae85ff7e3b81"
2649
+ integrity sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==
2650
2650
  dependencies:
2651
2651
  dom-walk "^0.1.0"
2652
2652
 
@@ -3022,7 +3022,7 @@ rfdc@^1.3.0:
3022
3022
  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
3023
3023
  integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
3024
3024
 
3025
- rimraf@^3.0.0, rimraf@^3.0.2:
3025
+ rimraf@^3.0.2:
3026
3026
  version "3.0.2"
3027
3027
  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
3028
3028
  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -3280,11 +3280,9 @@ terser@^5.31.1:
3280
3280
  source-map-support "~0.5.20"
3281
3281
 
3282
3282
  tmp@^0.2.1:
3283
- version "0.2.1"
3284
- resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
3285
- integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
3286
- dependencies:
3287
- rimraf "^3.0.0"
3283
+ version "0.2.4"
3284
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.4.tgz#c6db987a2ccc97f812f17137b36af2b6521b0d13"
3285
+ integrity sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==
3288
3286
 
3289
3287
  to-fast-properties@^2.0.0:
3290
3288
  version "2.0.0"