shopify_app 7.2.0 → 7.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +19 -12
- data/TROUBLESHOOTING.md +16 -0
- data/lib/generators/shopify_app/install/install_generator.rb +8 -4
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_app.rb +0 -2
- data/lib/shopify_app/login_protection.rb +35 -31
- data/lib/shopify_app/scripttags_manager.rb +24 -8
- data/lib/shopify_app/scripttags_manager_job.rb +2 -2
- data/lib/shopify_app/sessions_concern.rb +6 -15
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app/webhook_verification.rb +1 -1
- data/lib/shopify_app/webhooks_manager.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6802e72897eb705326a9cc6639506aa5eefb912
|
4
|
+
data.tar.gz: 76ae92534abcbbc635adcab460e6d7b72898a5ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22cb00e95696ddfce4f64cd77cbe422de626e17e654b151d89c83a2d0813469a4565f51725f848c557e70b58275f2dfb1f40e0600d2bd03f787fa97d38325ae7
|
7
|
+
data.tar.gz: fffd5d0b94281792e7911091060654f06192387b9a5d08f336a6ec18725af3094a65c4ff5d9e05771dd0fba7d326d04b17572f305d68a2f6cef93fe0cb30b49b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
7.2.2
|
2
|
+
-----
|
3
|
+
* Use postMessage to redirect parent iframe during authentication
|
4
|
+
https://github.com/Shopify/shopify_app/pull/366
|
5
|
+
|
6
|
+
7.2.1
|
7
|
+
-----
|
8
|
+
* Add support for dynamically generating scripttag URLs
|
9
|
+
* Bug-fix: `--application_name` and `--scope` generates proper Configuration even when options supplied to them contain whitespaces.
|
10
|
+
|
1
11
|
7.2.0
|
2
12
|
-----
|
3
13
|
* Disable application layout rendering for the `/login` page
|
data/README.md
CHANGED
@@ -127,8 +127,10 @@ $ rails generate shopify_app:install --api_key <your_api_key> --secret <your_app
|
|
127
127
|
```
|
128
128
|
|
129
129
|
Other options include:
|
130
|
-
* `application_name` - the name of your app
|
131
|
-
* `scope` - the Oauth access scope required for your app, eg
|
130
|
+
* `application_name` - the name of your app, it can be supplied with or without double-quotes if a whitespace is present. (e.g. `--application_name Example App` or `--application_name "Example App"`)
|
131
|
+
* `scope` - the Oauth access scope required for your app, eg **read_products, write_orders**. *Multiple options* need to be delimited by a comma-space, and can be supplied with or without double-quotes
|
132
|
+
(e.g. `--scope read_products, write_orders, write_products` or `--scope "read_products, write_orders, write_products"`)
|
133
|
+
For more information, refer the [docs](http://docs.shopify.com/api/tutorials/oauth).
|
132
134
|
* `embedded` - the default is to generate an [embedded app](http://docs.shopify.com/embedded-app-sdk), if you want a legacy non-embedded app then set this to false, `--embedded false`
|
133
135
|
|
134
136
|
You can update any of these settings later on easily, the arguments are simply for convenience.
|
@@ -225,7 +227,7 @@ ShopifyApp can manage your app's webhooks for you by setting which webhooks you
|
|
225
227
|
```ruby
|
226
228
|
ShopifyApp.configure do |config|
|
227
229
|
config.webhooks = [
|
228
|
-
{topic: 'carts/update', address: 'example-app.com/webhooks/carts_update'}
|
230
|
+
{topic: 'carts/update', address: 'https://example-app.com/webhooks/carts_update'}
|
229
231
|
]
|
230
232
|
end
|
231
233
|
```
|
@@ -234,6 +236,16 @@ When the oauth callback is completed successfully ShopifyApp will queue a backgr
|
|
234
236
|
|
235
237
|
ShopifyApp also provides a WebhooksController that receives webhooks and queues a job based on the webhook url. For example if you register the webhook from above then all you need to do is create a job called `CartsUpdateJob`. The job will be queued with 2 params `shop_domain` and `webhook` which is the webhook body.
|
236
238
|
|
239
|
+
If you are only interested in particular fields, you can optionally filter the data sent by Shopify by specifying the `fields` parameter in `config/webhooks`. Note that you will still receive a webhook request from Shopify every time the resource is updated, but only the specified fields will be sent.
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
ShopifyApp.configure do |config|
|
243
|
+
config.webhooks = [
|
244
|
+
{topic: 'products/update', address: 'https://example-app.com/webhooks/products_update', fields: ['title', 'vendor']}
|
245
|
+
]
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
237
249
|
If you'd rather implement your own controller then you'll want to use the WebhookVerfication module to verify your webhooks:
|
238
250
|
|
239
251
|
```ruby
|
@@ -268,12 +280,15 @@ As with webhooks, ShopifyApp can manage your app's scripttags for you by setting
|
|
268
280
|
ShopifyApp.configure do |config|
|
269
281
|
config.scripttags = [
|
270
282
|
{event:'onload', src: 'https://my-shopifyapp.herokuapp.com/fancy.js'}
|
283
|
+
{event:'onload', src: ->(domain) { dynamic_tag_url(domain) } }
|
271
284
|
]
|
272
285
|
end
|
273
286
|
```
|
274
287
|
|
275
288
|
Scripttags are created in the same way as the Webhooks, with a background job which will create the required scripttags.
|
276
289
|
|
290
|
+
If `src` responds to `call` its return value will be used as the scripttag's source. It will be called on scripttag creation and deletion.
|
291
|
+
|
277
292
|
ShopifyApp::SessionRepository
|
278
293
|
-----------------------------
|
279
294
|
|
@@ -310,15 +325,7 @@ Create your app proxy url in the [Shopify Partners' Dashboard](https://app.shopi
|
|
310
325
|
Troubleshooting
|
311
326
|
---------------
|
312
327
|
|
313
|
-
|
314
|
-
|
315
|
-
Rails uses spring by default to speed up development. To run the generator, spring has to be stopped:
|
316
|
-
|
317
|
-
```sh
|
318
|
-
$ bundle exec spring stop
|
319
|
-
```
|
320
|
-
|
321
|
-
Run shopify_app generator again.
|
328
|
+
see [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
|
322
329
|
|
323
330
|
Testing an embedded app outside the Shopify admin
|
324
331
|
-------------------------------------------------
|
data/TROUBLESHOOTING.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Troubleshooting Shopify App
|
2
|
+
===========
|
3
|
+
|
4
|
+
### Generator shopify_app:install hangs
|
5
|
+
|
6
|
+
Rails uses spring by default to speed up development. To run the generator, spring has to be stopped:
|
7
|
+
|
8
|
+
```sh
|
9
|
+
$ bundle exec spring stop
|
10
|
+
```
|
11
|
+
|
12
|
+
Run shopify_app generator again.
|
13
|
+
|
14
|
+
### App installation fails with 'The page you’re looking for could not be found' if the app was installed before
|
15
|
+
|
16
|
+
This issue can occur when the session (the model you set as `ShopifyApp::SessionRepository.storage`) isn't deleted when the user uninstalls your app. A possible fix for this is listening to the `app/uninstalled` webhook and deleting the corresponding session in the webhook handler.
|
@@ -7,17 +7,17 @@ module ShopifyApp
|
|
7
7
|
include Rails::Generators::Migration
|
8
8
|
source_root File.expand_path('../templates', __FILE__)
|
9
9
|
|
10
|
-
class_option :application_name, type: :
|
10
|
+
class_option :application_name, type: :array, default: ['My', 'Shopify', 'App']
|
11
11
|
class_option :api_key, type: :string, default: '<api_key>'
|
12
12
|
class_option :secret, type: :string, default: '<secret>'
|
13
|
-
class_option :scope, type: :
|
13
|
+
class_option :scope, type: :array, default: ['read_orders,', 'read_products']
|
14
14
|
class_option :embedded, type: :string, default: 'true'
|
15
15
|
|
16
16
|
def create_shopify_app_initializer
|
17
|
-
@application_name = options['application_name']
|
17
|
+
@application_name = format_array_argument(options['application_name'])
|
18
18
|
@api_key = options['api_key']
|
19
19
|
@secret = options['secret']
|
20
|
-
@scope = options['scope']
|
20
|
+
@scope = format_array_argument(options['scope'])
|
21
21
|
|
22
22
|
template 'shopify_app.rb', 'config/initializers/shopify_app.rb'
|
23
23
|
end
|
@@ -61,6 +61,10 @@ module ShopifyApp
|
|
61
61
|
def embedded_app?
|
62
62
|
options['embedded'] == 'true'
|
63
63
|
end
|
64
|
+
|
65
|
+
def format_array_argument(array)
|
66
|
+
array.join(' ').tr('"', '')
|
67
|
+
end
|
64
68
|
end
|
65
69
|
end
|
66
70
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<head>
|
4
4
|
<meta charset="utf-8" />
|
5
5
|
<% application_name = ShopifyApp.configuration.application_name %>
|
6
|
-
<title><%= application_name
|
6
|
+
<title><%= application_name %></title>
|
7
7
|
<%= stylesheet_link_tag 'application' %>
|
8
8
|
<%= javascript_include_tag 'application', "data-turbolinks-track" => true %>
|
9
9
|
<%= csrf_meta_tags %>
|
@@ -39,65 +39,69 @@ module ShopifyApp
|
|
39
39
|
head :unauthorized
|
40
40
|
else
|
41
41
|
session[:return_to] = request.fullpath if request.get?
|
42
|
-
|
42
|
+
redirect_to main_or_engine_login_url(shop: params[:shop])
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
def close_session
|
47
47
|
session[:shopify] = nil
|
48
48
|
session[:shopify_domain] = nil
|
49
|
-
|
49
|
+
redirect_to main_or_engine_login_url(shop: params[:shop])
|
50
50
|
end
|
51
51
|
|
52
52
|
def main_or_engine_login_url(params = {})
|
53
53
|
main_app.login_url(params)
|
54
|
-
rescue NoMethodError
|
54
|
+
rescue NoMethodError
|
55
55
|
shopify_app.login_url(params)
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
58
|
+
def fullpage_redirect_to(url)
|
59
|
+
if ShopifyApp.configuration.embedded_app?
|
60
|
+
render inline: redirection_javascript(url)
|
61
|
+
else
|
62
|
+
redirect_to url
|
63
|
+
end
|
64
|
+
end
|
61
65
|
|
62
|
-
|
66
|
+
def redirection_javascript(url)
|
67
|
+
%(
|
63
68
|
<!DOCTYPE html>
|
64
69
|
<html lang="en">
|
65
70
|
<head>
|
66
71
|
<meta charset="utf-8" />
|
72
|
+
<base target="_top">
|
67
73
|
<title>Redirecting…</title>
|
68
74
|
<script type="text/javascript">
|
69
|
-
|
75
|
+
|
76
|
+
// If the current window is the 'parent', change the URL by setting location.href
|
77
|
+
if (window.top == window.self) {
|
78
|
+
window.top.location.href = #{url.to_json};
|
79
|
+
|
80
|
+
// If the current window is the 'child', change the parent's URL with postMessage
|
81
|
+
} else {
|
82
|
+
data = JSON.stringify({
|
83
|
+
message: 'Shopify.API.remoteRedirect',
|
84
|
+
data: { location: window.location.origin + #{url.to_json} }
|
85
|
+
});
|
86
|
+
window.parent.postMessage(data, "https://#{sanitized_shop_name}");
|
87
|
+
}
|
88
|
+
|
70
89
|
</script>
|
71
90
|
</head>
|
72
91
|
<body>
|
73
92
|
</body>
|
74
93
|
</html>
|
75
|
-
)
|
94
|
+
)
|
76
95
|
end
|
77
96
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
97
|
+
def sanitized_shop_name
|
98
|
+
@sanitized_shop_name ||= sanitize_shop_param(params)
|
99
|
+
end
|
81
100
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
<html lang="en">
|
86
|
-
<head>
|
87
|
-
<meta charset="utf-8" />
|
88
|
-
<base target="_top">
|
89
|
-
<title>Redirecting…</title>
|
90
|
-
<script type="text/javascript">
|
91
|
-
window.top.location.href = #{url_json};
|
92
|
-
</script>
|
93
|
-
</head>
|
94
|
-
<body>
|
95
|
-
</body>
|
96
|
-
</html>
|
97
|
-
)
|
98
|
-
else
|
99
|
-
redirect_to_with_fallback url
|
100
|
-
end
|
101
|
+
def sanitize_shop_param(params)
|
102
|
+
return unless params[:shop].present?
|
103
|
+
ShopifyApp::Utils.sanitize_shop_domain(params[:shop])
|
101
104
|
end
|
105
|
+
|
102
106
|
end
|
103
107
|
end
|
@@ -6,14 +6,25 @@ module ShopifyApp
|
|
6
6
|
ShopifyApp::ScripttagsManagerJob.perform_later(
|
7
7
|
shop_domain: shop_domain,
|
8
8
|
shop_token: shop_token,
|
9
|
-
|
9
|
+
# Procs cannot be serialized so we interpolate now, if necessary
|
10
|
+
scripttags: build_src(scripttags, shop_domain)
|
10
11
|
)
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
+
def self.build_src(scripttags, domain)
|
15
|
+
scripttags.map do |tag|
|
16
|
+
next tag unless tag[:src].respond_to?(:call)
|
17
|
+
tag = tag.dup
|
18
|
+
tag[:src] = tag[:src].call(domain)
|
19
|
+
tag
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :required_scripttags, :shop_domain
|
14
24
|
|
15
|
-
def initialize(scripttags)
|
25
|
+
def initialize(scripttags, shop_domain)
|
16
26
|
@required_scripttags = scripttags
|
27
|
+
@shop_domain = shop_domain
|
17
28
|
end
|
18
29
|
|
19
30
|
def recreate_scripttags!
|
@@ -24,14 +35,15 @@ module ShopifyApp
|
|
24
35
|
def create_scripttags
|
25
36
|
return unless required_scripttags.present?
|
26
37
|
|
27
|
-
|
38
|
+
expanded_scripttags.each do |scripttag|
|
28
39
|
create_scripttag(scripttag) unless scripttag_exists?(scripttag[:src])
|
29
40
|
end
|
30
41
|
end
|
31
42
|
|
32
43
|
def destroy_scripttags
|
33
|
-
|
34
|
-
|
44
|
+
scripttags = expanded_scripttags
|
45
|
+
ShopifyAPI::ScriptTag.all.each do |tag|
|
46
|
+
ShopifyAPI::ScriptTag.delete(tag.id) if is_required_scripttag?(scripttags, tag)
|
35
47
|
end
|
36
48
|
|
37
49
|
@current_scripttags = nil
|
@@ -39,8 +51,12 @@ module ShopifyApp
|
|
39
51
|
|
40
52
|
private
|
41
53
|
|
42
|
-
def
|
43
|
-
|
54
|
+
def expanded_scripttags
|
55
|
+
self.class.build_src(required_scripttags, shop_domain)
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_required_scripttag?(scripttags, tag)
|
59
|
+
scripttags.map{ |w| w[:src] }.include? tag.src
|
44
60
|
end
|
45
61
|
|
46
62
|
def create_scripttag(attributes)
|
@@ -2,12 +2,12 @@ module ShopifyApp
|
|
2
2
|
class ScripttagsManagerJob < ActiveJob::Base
|
3
3
|
|
4
4
|
queue_as do
|
5
|
-
ShopifyApp.configuration.
|
5
|
+
ShopifyApp.configuration.scripttags_manager_queue_name
|
6
6
|
end
|
7
7
|
|
8
8
|
def perform(shop_domain:, shop_token:, scripttags:)
|
9
9
|
ShopifyAPI::Session.temp(shop_domain, shop_token) do
|
10
|
-
manager = ScripttagsManager.new(scripttags)
|
10
|
+
manager = ScripttagsManager.new(scripttags, shop_domain)
|
11
11
|
manager.create_scripttags
|
12
12
|
end
|
13
13
|
end
|
@@ -22,10 +22,10 @@ module ShopifyApp
|
|
22
22
|
install_scripttags
|
23
23
|
|
24
24
|
flash[:notice] = I18n.t('.logged_in')
|
25
|
-
|
25
|
+
redirect_to return_address
|
26
26
|
else
|
27
27
|
flash[:error] = I18n.t('could_not_log_in')
|
28
|
-
|
28
|
+
redirect_to login_url
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -33,16 +33,16 @@ module ShopifyApp
|
|
33
33
|
session[:shopify] = nil
|
34
34
|
session[:shopify_domain] = nil
|
35
35
|
flash[:notice] = I18n.t('.logged_out')
|
36
|
-
|
36
|
+
redirect_to login_url
|
37
37
|
end
|
38
38
|
|
39
39
|
protected
|
40
40
|
|
41
41
|
def authenticate
|
42
|
-
if
|
43
|
-
fullpage_redirect_to "#{main_app.root_path}auth/shopify?shop=#{
|
42
|
+
if sanitized_shop_name.present?
|
43
|
+
fullpage_redirect_to "#{main_app.root_path}auth/shopify?shop=#{sanitized_shop_name}"
|
44
44
|
else
|
45
|
-
|
45
|
+
redirect_to return_address
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -88,14 +88,5 @@ module ShopifyApp
|
|
88
88
|
session.delete(:return_to) || main_app.root_url
|
89
89
|
end
|
90
90
|
|
91
|
-
def sanitized_shop_name
|
92
|
-
@sanitized_shop_name ||= sanitize_shop_param(params)
|
93
|
-
end
|
94
|
-
|
95
|
-
def sanitize_shop_param(params)
|
96
|
-
return unless params[:shop].present?
|
97
|
-
ShopifyApp::Utils.sanitize_shop_domain(params[:shop])
|
98
|
-
end
|
99
|
-
|
100
91
|
end
|
101
92
|
end
|
data/lib/shopify_app/version.rb
CHANGED
@@ -22,7 +22,7 @@ module ShopifyApp
|
|
22
22
|
def hmac_valid?(data)
|
23
23
|
secret = ShopifyApp.configuration.secret
|
24
24
|
digest = OpenSSL::Digest.new('sha256')
|
25
|
-
ActiveSupport::SecurityUtils.
|
25
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
26
26
|
shopify_hmac,
|
27
27
|
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, data)).strip
|
28
28
|
)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify_app
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.2.
|
4
|
+
version: 7.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- README.md
|
142
142
|
- RELEASING
|
143
143
|
- Rakefile
|
144
|
+
- TROUBLESHOOTING.md
|
144
145
|
- app/controllers/shopify_app/authenticated_controller.rb
|
145
146
|
- app/controllers/shopify_app/sessions_controller.rb
|
146
147
|
- app/controllers/shopify_app/webhooks_controller.rb
|