shopify_app 7.2.0 → 8.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.babelrc +5 -0
- data/.github/CODEOWNERS +1 -0
- data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +0 -0
- data/.github/probots.yml +2 -0
- data/.gitignore +5 -0
- data/.nvmrc +1 -0
- data/.rubocop.yml +10 -0
- data/.ruby-version +1 -0
- data/.travis.yml +24 -12
- data/CHANGELOG.md +151 -0
- data/Gemfile +2 -0
- data/README.md +167 -68
- data/app/assets/images/storage_access.svg +2 -0
- data/app/assets/javascripts/shopify_app/enable_cookies.js +3 -0
- data/app/assets/javascripts/shopify_app/itp_helper.js +40 -0
- data/app/assets/javascripts/shopify_app/partition_cookies.js +7 -0
- data/app/assets/javascripts/shopify_app/redirect.js +33 -0
- data/app/assets/javascripts/shopify_app/request_storage_access.js +3 -0
- data/app/assets/javascripts/shopify_app/storage_access.js +121 -0
- data/app/assets/javascripts/shopify_app/storage_access_redirect.js +17 -0
- data/app/assets/javascripts/shopify_app/top_level.js +2 -0
- data/app/assets/javascripts/shopify_app/top_level_interaction.js +11 -0
- data/app/controllers/shopify_app/authenticated_controller.rb +5 -2
- data/app/controllers/shopify_app/callback_controller.rb +92 -0
- data/app/controllers/shopify_app/sessions_controller.rb +120 -2
- data/app/controllers/shopify_app/webhooks_controller.rb +11 -3
- data/app/views/shopify_app/partials/_button_styles.html.erb +104 -0
- data/app/views/shopify_app/partials/_card_styles.html.erb +33 -0
- data/app/views/shopify_app/partials/_empty_state_styles.html.erb +129 -0
- data/app/views/shopify_app/partials/_layout_styles.html.erb +167 -0
- data/app/views/shopify_app/partials/_typography_styles.html.erb +35 -0
- data/app/views/shopify_app/sessions/enable_cookies.html.erb +59 -0
- data/app/views/shopify_app/sessions/new.html.erb +88 -60
- data/app/views/shopify_app/sessions/request_storage_access.html.erb +67 -0
- data/app/views/shopify_app/sessions/top_level_interaction.html.erb +63 -0
- data/app/views/shopify_app/shared/redirect.html.erb +22 -0
- data/config/locales/de.yml +22 -0
- data/config/locales/en.yml +12 -1
- data/config/locales/es.yml +21 -3
- data/config/locales/fr.yml +23 -0
- data/config/locales/it.yml +22 -0
- data/config/locales/ja.yml +17 -0
- data/config/locales/nl.yml +21 -0
- data/config/locales/pt-BR.yml +22 -0
- data/config/locales/zh-CN.yml +16 -0
- data/config/locales/zh-TW.yml +17 -0
- data/config/routes.rb +11 -1
- data/{QUICKSTART.md → docs/Quickstart.md} +26 -23
- data/docs/Releasing.md +18 -0
- data/docs/Troubleshooting.md +16 -0
- data/karma.conf.js +43 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +45 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +10 -0
- data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +1 -0
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +14 -0
- data/lib/generators/shopify_app/home_controller/templates/shopify_app_ready_script.html.erb +1 -5
- data/lib/generators/shopify_app/install/install_generator.rb +10 -16
- data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +13 -9
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_app.rb +5 -3
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb +19 -4
- data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +16 -0
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +17 -0
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +42 -0
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +5 -9
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/{create_shops.rb → create_shops.erb} +1 -1
- data/lib/generators/shopify_app/shop_model/templates/shop.rb +0 -1
- data/lib/shopify_app/configuration.rb +27 -8
- data/lib/shopify_app/{app_proxy_verification.rb → controller_concerns/app_proxy_verification.rb} +2 -7
- data/lib/shopify_app/controller_concerns/embedded_app.rb +19 -0
- data/lib/shopify_app/controller_concerns/itp.rb +45 -0
- data/lib/shopify_app/controller_concerns/localization.rb +22 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +135 -0
- data/lib/shopify_app/{webhook_verification.rb → controller_concerns/webhook_verification.rb} +11 -12
- data/lib/shopify_app/engine.rb +10 -0
- data/lib/shopify_app/{scripttags_manager_job.rb → jobs/scripttags_manager_job.rb} +2 -2
- data/lib/shopify_app/{webhooks_manager_job.rb → jobs/webhooks_manager_job.rb} +0 -0
- data/lib/shopify_app/{scripttags_manager.rb → managers/scripttags_manager.rb} +24 -8
- data/lib/shopify_app/{webhooks_manager.rb → managers/webhooks_manager.rb} +1 -1
- data/lib/shopify_app/session/in_memory_session_store.rb +27 -0
- data/lib/shopify_app/{shopify_session_repository.rb → session/session_repository.rb} +0 -0
- data/lib/shopify_app/{session_storage.rb → session/session_storage.rb} +9 -0
- data/lib/shopify_app/utils.rb +2 -2
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +21 -16
- data/package-lock.json +23 -0
- data/package.json +28 -0
- data/service.yml +7 -0
- data/shipit.rubygems.yml +2 -0
- data/shopify_app.gemspec +6 -5
- data/translation.yml +7 -0
- data/webpack.config.js +24 -0
- data/yarn.lock +4594 -0
- metadata +92 -35
- data/Gemfile.rails50 +0 -5
- data/Gemfile.ruby22 +0 -6
- data/Gemfile.ruby22.rails50 +0 -9
- data/RELEASING +0 -13
- data/lib/generators/shopify_app/install/templates/shopify_session_repository.rb +0 -23
- data/lib/generators/shopify_app/shop_model/templates/shopify_session_repository.rb +0 -7
- data/lib/shopify_app/in_memory_session_store.rb +0 -25
- data/lib/shopify_app/login_protection.rb +0 -103
- data/lib/shopify_app/sessions_concern.rb +0 -101
- data/lib/shopify_app/shop.rb +0 -15
data/README.md
CHANGED
@@ -8,14 +8,16 @@ Shopify App
|
|
8
8
|
|
9
9
|
Shopify Application Rails engine and generator
|
10
10
|
|
11
|
+
#### NOTE : Versions 8.0.0 through 8.2.3 contained a CSRF vulnerability that was addressed in version 8.2.4. Please update to version 8.2.4 if you're using an old version.
|
11
12
|
|
12
13
|
Table of Contents
|
13
14
|
-----------------
|
14
15
|
* [**Description**](#description)
|
15
16
|
* [**Quickstart**](#quickstart)
|
16
17
|
* [**Becoming a Shopify App Developer**](#becoming-a-shopify-app-developer)
|
18
|
+
* [**App Tunneling**](#app-tunneling)
|
17
19
|
* [**Installation**](#installation)
|
18
|
-
* [Rails
|
20
|
+
* [Rails Compatibility](#rails-compatibility)
|
19
21
|
* [**Generators**](#generators)
|
20
22
|
* [Default Generator](#default-generator)
|
21
23
|
* [Install Generator](#install-generator)
|
@@ -27,6 +29,7 @@ Table of Contents
|
|
27
29
|
* [**Managing Api Keys**](#managing-api-keys)
|
28
30
|
* [**WebhooksManager**](#webhooksmanager)
|
29
31
|
* [**ScripttagsManager**](#scripttagsmanager)
|
32
|
+
* [**AfterAuthenticate Job**](#afterauthenticate-job)
|
30
33
|
* [**ShopifyApp::SessionRepository**](#shopifyappsessionrepository)
|
31
34
|
* [**AuthenticatedController**](#authenticatedcontroller)
|
32
35
|
* [**AppProxyVerification**](#appproxyverification)
|
@@ -34,7 +37,6 @@ Table of Contents
|
|
34
37
|
* [**Troubleshooting**](#troubleshooting)
|
35
38
|
* [Generator shopify_app:install hangs](#generator-shopify_appinstall-hangs)
|
36
39
|
* [**Testing an embedded app outside the Shopify admin**](#testing-an-embedded-app-outside-the-shopify-admin)
|
37
|
-
* [**App Tunneling**](#app-tunneling)
|
38
40
|
* [**Questions or problems?**](#questions-or-problems)
|
39
41
|
|
40
42
|
|
@@ -52,30 +54,26 @@ Quickstart
|
|
52
54
|
|
53
55
|
Check out this screencast on how to create and deploy a new Shopify App to Heroku in 5 minutes:
|
54
56
|
|
55
|
-
[https://
|
57
|
+
[https://www.youtube.com/watch?v=yGxeoAHlQOg](https://www.youtube.com/watch?v=yGxeoAHlQOg)
|
56
58
|
|
57
|
-
Or if you prefer text instructions the steps in the video are written out [here](https://github.com/Shopify/shopify_app/blob/master/
|
59
|
+
Or if you prefer text instructions the steps in the video are written out [here](https://github.com/Shopify/shopify_app/blob/master/docs/Quickstart.md)
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
-
If you don't have a Shopify Partner account yet head over to http://shopify.com/partners to create one, you'll need it before you can start developing apps.
|
62
|
-
|
63
|
-
Once you have a Partner account create a new application to get an Api key and other Api credentials. To create a development application set the Application Callback URL to
|
61
|
+
App Tunneling
|
62
|
+
-------------
|
64
63
|
|
65
|
-
|
66
|
-
http://localhost:3000/
|
67
|
-
```
|
64
|
+
Your local app needs to be accessible from the public Internet in order to install it on a shop, use the [App Proxy Controller](#app-proxy-controller-generator) or receive Webhooks. Use a tunneling service like [ngrok](https://ngrok.com/), [Forward](https://forwardhq.com/), [Beeceptor](https://beeceptor.com/), [Mockbin](http://mockbin.org/), [Hookbin](https://hookbin.com/), etc.
|
68
65
|
|
69
|
-
|
66
|
+
For example with [ngrok](https://ngrok.com/), run this command to set up proxying to Rails' default port:
|
70
67
|
|
68
|
+
```sh
|
69
|
+
ngrok http 3000
|
71
70
|
```
|
72
|
-
http://localhost:3000/auth/shopify/callback
|
73
|
-
```
|
74
|
-
|
75
|
-
This way you'll be able to run the app on your local machine.
|
76
71
|
|
77
|
-
|
72
|
+
Becoming a Shopify App Developer
|
73
|
+
--------------------------------
|
74
|
+
If you don't have a Shopify Partner account yet head over to http://shopify.com/partners to create one, you'll need it before you can start developing apps.
|
78
75
|
|
76
|
+
Once you have a Partner account create a new application to get an API key and other API credentials. To create a development application set the `App URL` to the URL provided by [your tunnel](#app-tunneling) or `http://localhost:3000/` if you are not embeddeding your app inside the admin or receiving webhooks and the `Whitelisted redirection URL(s)` to contain `<App URL>/auth/shopify/callback`. Ensure you are using `https://` URLs if you are using tunneling.
|
79
77
|
|
80
78
|
Installation
|
81
79
|
------------
|
@@ -94,14 +92,9 @@ $ bundle install
|
|
94
92
|
Now we are ready to run any of the shopify_app generators. The following section explains the generators and what they can do.
|
95
93
|
|
96
94
|
|
97
|
-
#### Rails
|
95
|
+
#### Rails Compatibility
|
98
96
|
|
99
|
-
shopify_app is compatible with Rails 5
|
100
|
-
|
101
|
-
```ruby
|
102
|
-
gem 'shopify_app'
|
103
|
-
gem 'activeresource', github: 'rails/activeresource'
|
104
|
-
```
|
97
|
+
The lastest version of shopify_app is compatible with Rails `>= 5`. Use version `<= v7.2.8` if you need to work with Rails 4.
|
105
98
|
|
106
99
|
|
107
100
|
Generators
|
@@ -115,6 +108,7 @@ The default generator will run the `install`, `shop`, and `home_controller` gene
|
|
115
108
|
$ rails generate shopify_app --api_key <your_api_key> --secret <your_app_secret>
|
116
109
|
```
|
117
110
|
|
111
|
+
After running the generator, you will need to run `rake db:migrate` to add tables to your database. You can start your app with `bundle exec rails server` and install your app by visiting localhost.
|
118
112
|
|
119
113
|
### Install Generator
|
120
114
|
|
@@ -127,8 +121,10 @@ $ rails generate shopify_app:install --api_key <your_api_key> --secret <your_app
|
|
127
121
|
```
|
128
122
|
|
129
123
|
Other options include:
|
130
|
-
* `application_name` - the name of your app
|
131
|
-
* `scope` - the Oauth access scope required for your app, eg
|
124
|
+
* `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"`)
|
125
|
+
* `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
|
126
|
+
(e.g. `--scope read_products, write_orders, write_products` or `--scope "read_products, write_orders, write_products"`)
|
127
|
+
For more information, refer the [docs](http://docs.shopify.com/api/tutorials/oauth).
|
132
128
|
* `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
129
|
|
134
130
|
You can update any of these settings later on easily, the arguments are simply for convenience.
|
@@ -144,7 +140,7 @@ After running the `install` generator, you can start your app with `bundle exec
|
|
144
140
|
$ rails generate shopify_app:shop_model
|
145
141
|
```
|
146
142
|
|
147
|
-
The install generator doesn't create any database models for you
|
143
|
+
The `install` generator doesn't create any database tables or models for you. If you are starting a new app its quite likely that you will want a shops table and model to store the tokens when your app is installed (most of our internally developed apps do!). This generator creates a shop model and a migration. This model includes the `ShopifyApp::SessionStorage` concern which adds two methods to make it compatible as a `SessionRepository`. After running this generator you'll notice the `session_repository` in your `config/initializers/shopify_app.rb` will be set to the `Shop` model. This means that internally ShopifyApp will try and load tokens from this model.
|
148
144
|
|
149
145
|
*Note that you will need to run rake db:migrate after this generator*
|
150
146
|
|
@@ -174,7 +170,7 @@ The last group of generators are for your convenience if you want to start overr
|
|
174
170
|
Mounting the Engine
|
175
171
|
-------------------
|
176
172
|
|
177
|
-
Mounting the Engine will provide the basic routes to authenticating a shop with your
|
173
|
+
Mounting the Engine will provide the basic routes to authenticating a shop with your application. By default it will provide:
|
178
174
|
|
179
175
|
| Verb | Route | Action |
|
180
176
|
|--------|-------------------------------|------------------------------|
|
@@ -184,23 +180,58 @@ Mounting the Engine will provide the basic routes to authenticating a shop with
|
|
184
180
|
|GET |'/logout' |Logout |
|
185
181
|
|POST |'/webhooks/:type' |Webhook Callback |
|
186
182
|
|
183
|
+
### Nested Routes
|
187
184
|
|
188
|
-
The
|
185
|
+
The engine may also be mounted at a nested route, for example:
|
189
186
|
|
190
187
|
```ruby
|
191
|
-
mount ShopifyApp::Engine, at: '/
|
188
|
+
mount ShopifyApp::Engine, at: '/nested'
|
192
189
|
```
|
193
190
|
|
194
|
-
This will create the Shopify engine routes under the specified
|
191
|
+
This will create the Shopify engine routes under the specified subpath. You'll also need to make some updates to your `shopify_app.rb` and `omniauth.rb` initializers. First update the shopify_app initializer to include a custom `root_url` e.g.:
|
195
192
|
|
196
|
-
|
193
|
+
```ruby
|
194
|
+
ShopifyApp.configure do |config|
|
195
|
+
config.root_url = '/nested'
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
then update the omniauth initializer to include a custom `callback_path` e.g.:
|
197
200
|
|
198
201
|
```ruby
|
199
|
-
|
202
|
+
provider :shopify,
|
203
|
+
ShopifyApp.configuration.api_key,
|
204
|
+
ShopifyApp.configuration.secret,
|
205
|
+
scope: ShopifyApp.configuration.scope,
|
206
|
+
callback_path: '/nested/auth/shopify/callback'
|
207
|
+
```
|
208
|
+
|
209
|
+
### Custom login URL
|
200
210
|
|
201
|
-
shopify_app.
|
211
|
+
While you can customize the login view by creating a `/app/views/shopify_app/sessions/new.html.erb` file, you may also want to customize the URL entirely. You can modify your `shopify_app.rb` initializer to provide a custom `login_url` e.g.:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
ShopifyApp.configure do |config|
|
215
|
+
config.login_url = 'https://my.domain.com/nested/login'
|
216
|
+
end
|
202
217
|
```
|
203
218
|
|
219
|
+
Per User Authentication
|
220
|
+
-----------------------
|
221
|
+
To enable per user authentication you need to update the `omniauth.rb` initializer:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
provider :shopify,
|
225
|
+
ShopifyApp.configuration.api_key,
|
226
|
+
ShopifyApp.configuration.secret,
|
227
|
+
scope: ShopifyApp.configuration.scope,
|
228
|
+
per_user_permissions: true
|
229
|
+
```
|
230
|
+
|
231
|
+
The current Shopify user will be stored in the rails session at `session[:shopify_user]`
|
232
|
+
|
233
|
+
This will change the type of token that Shopify returns and it will only be valid for a short time. Read more about `Online access` [here](https://help.shopify.com/api/getting-started/authentication/oauth). Note that this means you won't be able to use this token to respond to Webhooks.
|
234
|
+
|
204
235
|
Managing Api Keys
|
205
236
|
-----------------
|
206
237
|
|
@@ -211,7 +242,7 @@ ShopifyApp.configure do |config|
|
|
211
242
|
config.application_name = 'Your app name' # Optional
|
212
243
|
config.api_key = ENV['SHOPIFY_CLIENT_API_KEY']
|
213
244
|
config.secret = ENV['SHOPIFY_CLIENT_API_SECRET']
|
214
|
-
config.scope = 'read_customers,
|
245
|
+
config.scope = 'read_customers, write_products'
|
215
246
|
config.embedded_app = true
|
216
247
|
end
|
217
248
|
```
|
@@ -220,34 +251,59 @@ end
|
|
220
251
|
WebhooksManager
|
221
252
|
---------------
|
222
253
|
|
223
|
-
ShopifyApp can manage your app's webhooks for you
|
254
|
+
ShopifyApp can manage your app's webhooks for you if you set which webhooks you require in the initializer:
|
224
255
|
|
225
256
|
```ruby
|
226
257
|
ShopifyApp.configure do |config|
|
227
258
|
config.webhooks = [
|
228
|
-
{topic: 'carts/update', address: 'example-app.com/webhooks/carts_update'}
|
259
|
+
{topic: 'carts/update', address: 'https://example-app.com/webhooks/carts_update'}
|
229
260
|
]
|
230
261
|
end
|
231
262
|
```
|
232
263
|
|
233
264
|
When the oauth callback is completed successfully ShopifyApp will queue a background job which will ensure all the specified webhooks exist for that shop. Because this runs on every oauth callback it means your app will always have the webhooks it needs even if the user uninstalls and re-installs the app.
|
234
265
|
|
235
|
-
ShopifyApp also provides a WebhooksController that receives webhooks and queues a job based on the
|
266
|
+
ShopifyApp also provides a WebhooksController that receives webhooks and queues a job based on the received topic. 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).
|
267
|
+
|
268
|
+
If you would like to namespace your jobs you may set `webhook_jobs_namespace` in the config. For example if your app handles webhooks from other ecommerce applications as well, and you want Shopify cart update webhooks to be processed by a job living in `jobs/shopify/webhooks/carts_update_job.rb` rather than `jobs/carts_update_job.rb`):
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
ShopifyApp.configure do |config|
|
272
|
+
config.webhook_jobs_namespace = 'shopify/webhooks'
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
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.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
ShopifyApp.configure do |config|
|
280
|
+
config.webhooks = [
|
281
|
+
{topic: 'products/update', address: 'https://example-app.com/webhooks/products_update', fields: ['title', 'vendor']}
|
282
|
+
]
|
283
|
+
end
|
284
|
+
```
|
236
285
|
|
237
|
-
If you'd rather implement your own controller then you'll want to use the WebhookVerfication module to verify your webhooks:
|
286
|
+
If you'd rather implement your own controller then you'll want to use the WebhookVerfication module to verify your webhooks, example:
|
238
287
|
|
239
288
|
```ruby
|
240
289
|
class CustomWebhooksController < ApplicationController
|
241
290
|
include ShopifyApp::WebhookVerification
|
242
291
|
|
243
292
|
def carts_update
|
244
|
-
|
245
|
-
|
293
|
+
params.permit!
|
294
|
+
SomeJob.perform_later(shop_domain: shop_domain, webhook: webhook_params.to_h)
|
295
|
+
head :no_content
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
def webhook_params
|
301
|
+
params.except(:controller, :action, :type)
|
246
302
|
end
|
247
303
|
end
|
248
304
|
```
|
249
305
|
|
250
|
-
The module skips the `verify_authenticity_token` before_action and adds an action to verify that the webhook came from Shopify.
|
306
|
+
The module skips the `verify_authenticity_token` before_action and adds an action to verify that the webhook came from Shopify. You can now add a post route to your application pointing to the controller and action to accept the webhook data from Shopify.
|
251
307
|
|
252
308
|
The WebhooksManager uses ActiveJob, if ActiveJob is not configured then by default Rails will run the jobs inline. However it is highly recommended to configure a proper background processing queue like sidekiq or resque in production.
|
253
309
|
|
@@ -267,24 +323,84 @@ As with webhooks, ShopifyApp can manage your app's scripttags for you by setting
|
|
267
323
|
```ruby
|
268
324
|
ShopifyApp.configure do |config|
|
269
325
|
config.scripttags = [
|
270
|
-
{event:'onload', src: 'https://my-shopifyapp.herokuapp.com/fancy.js'}
|
326
|
+
{event:'onload', src: 'https://my-shopifyapp.herokuapp.com/fancy.js'},
|
327
|
+
{event:'onload', src: ->(domain) { dynamic_tag_url(domain) } }
|
271
328
|
]
|
272
329
|
end
|
273
330
|
```
|
274
331
|
|
332
|
+
You also need to have write_script_tags permission in the config scope in order to add script tags automatically:
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
config.scope = '... , write_script_tags'
|
336
|
+
```
|
337
|
+
|
275
338
|
Scripttags are created in the same way as the Webhooks, with a background job which will create the required scripttags.
|
276
339
|
|
340
|
+
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.
|
341
|
+
|
342
|
+
AfterAuthenticate Job
|
343
|
+
---------------------
|
344
|
+
|
345
|
+
If your app needs to perform specific actions after it is installed ShopifyApp can queue or run a job of your choosing (note that we already provide support for automatically creating Webhooks and Scripttags). To configure the after authenticate job update your initializer as follows:
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
ShopifyApp.configure do |config|
|
349
|
+
config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob }
|
350
|
+
end
|
351
|
+
```
|
352
|
+
|
353
|
+
If you need the job to run synchronously add the `inline` flag:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
ShopifyApp.configure do |config|
|
357
|
+
config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true }
|
358
|
+
end
|
359
|
+
```
|
360
|
+
|
361
|
+
We've also provided a generator which creates a skeleton job and updates the initializer for you:
|
362
|
+
|
363
|
+
```
|
364
|
+
bin/rails g shopify_app:add_after_authenticate_job
|
365
|
+
```
|
366
|
+
|
367
|
+
RotateShopifyTokenJob
|
368
|
+
---------------------
|
369
|
+
|
370
|
+
If your Shopify secret key is leaked, you can use the RotateShopifyTokenJob to perform [API Credential Rotation](https://help.shopify.com/en/api/getting-started/authentication/oauth/api-credential-rotation).
|
371
|
+
|
372
|
+
Before running the job, you'll need to generate a new secret key from your Shopify Partner dashboard, and update the `/config/initializers/shopify_app.rb` to hold your new and old secret keys:
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
config.secret = Rails.application.secrets.shopify_secret
|
376
|
+
config.old_secret = Rails.application.secrets.old_shopify_secret
|
377
|
+
```
|
378
|
+
|
379
|
+
We've provided a generator which creates the job and an example rake task:
|
380
|
+
|
381
|
+
```sh
|
382
|
+
bin/rails g shopify_app:rotate_shopify_token_job
|
383
|
+
```
|
384
|
+
|
385
|
+
The generated rake task will be found at `lib/tasks/shopify/rotate_shopify_token.rake` and is provided strictly for example purposes. It might not work with your application out of the box without some configuration.
|
386
|
+
|
387
|
+
⚠️ Note: if you are updating `shopify_app` from a version prior to 8.4.2 (and do not wish to run the default/install generator again), you will need to add [the following line](https://github.com/Shopify/shopify_app/blob/4f7e6cca2a472d8f7af44b938bd0fcafe4d8e88a/lib/generators/shopify_app/install/templates/shopify_provider.rb#L18) to `config/intializers/omniauth.rb`:
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
|
391
|
+
```
|
392
|
+
|
277
393
|
ShopifyApp::SessionRepository
|
278
394
|
-----------------------------
|
279
395
|
|
280
|
-
`ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are retrieved and stored for
|
396
|
+
`ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are retrieved and stored for shops. The `SessionRepository` is configured in the `config/initializers/shopify_app.rb` file and can be set to any object that implements `self.store(shopify_session)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. See either the `ShopifyApp::InMemorySessionStore` class or the `ShopifyApp::SessionStorage` concern for examples.
|
281
397
|
|
282
|
-
If you only run the install generator then by default you will have an in memory store but it **won't work** on multi-server environments including Heroku. If you ran all the generators including the shop_model generator then the Shop model itself will be the `SessionRepository`. If you look at the implementation of the generated shop model you'll see that this gem provides
|
398
|
+
If you only run the install generator then by default you will have an in memory store but it **won't work** on multi-server environments including Heroku. If you ran all the generators including the shop_model generator then the `Shop` model itself will be the `SessionRepository`. If you look at the implementation of the generated shop model you'll see that this gem provides a concern for the `SessionRepository`. You can use this concern on any model that responds to `shopify_domain` and `shopify_token`.
|
283
399
|
|
284
400
|
AuthenticatedController
|
285
401
|
-----------------------
|
286
402
|
|
287
|
-
The engine includes a controller called `ShopifyApp::AuthenticatedController` which inherits from `
|
403
|
+
The engine includes a controller called `ShopifyApp::AuthenticatedController` which inherits from `ActionController::Base`. It adds some before_filters which ensure the user is authenticated and will redirect to the login page if not. It is best practice to have all controllers that belong to the Shopify part of your app inherit from this controller. The HomeController that is generated already inherits from AuthenticatedController.
|
288
404
|
|
289
405
|
AppProxyVerification
|
290
406
|
--------------------
|
@@ -310,36 +426,19 @@ Create your app proxy url in the [Shopify Partners' Dashboard](https://app.shopi
|
|
310
426
|
Troubleshooting
|
311
427
|
---------------
|
312
428
|
|
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.
|
429
|
+
see [TROUBLESHOOTING.md](https://github.com/Shopify/shopify_app/blob/master/docs/Troubleshooting.md)
|
322
430
|
|
323
431
|
Testing an embedded app outside the Shopify admin
|
324
432
|
-------------------------------------------------
|
325
433
|
|
326
|
-
By default, loading your embedded app will redirect to the Shopify admin, with the app view loaded in an `iframe`. If you need to load your app outside of the Shopify admin (e.g., for performance testing), you can change `forceRedirect:
|
434
|
+
By default, loading your embedded app will redirect to the Shopify admin, with the app view loaded in an `iframe`. If you need to load your app outside of the Shopify admin (e.g., for performance testing), you can change `forceRedirect: true` to `false` in `ShopifyApp.init` block in the `embedded_app` view. To keep the redirect on in production but off in your `development` and `test` environments, you can use:
|
327
435
|
|
328
436
|
```javascript
|
329
437
|
forceRedirect: <%= Rails.env.development? || Rails.env.test? ? 'false' : 'true' %>
|
330
438
|
```
|
331
439
|
|
332
|
-
App Tunneling
|
333
|
-
-------------
|
334
|
-
|
335
|
-
For certain features like Application Proxy or Webhooks to receive requests from Shopify, your app needs to be on a publicly visible URL. This can be a hurdle during development or testing on a local machine. Fortunately, this can be overcome by employing a tunneling service like [Forward](https://forwardhq.com/), [RequestBin](requestb.in/), [ngrok](https://ngrok.com/) etc. These tools allow you to create a secure tunnel from the public Internet to your local machine.
|
336
|
-
|
337
|
-
Tunneling is also useful for working the the embedded app sdk to solve mixed content issues since most tunnles provide ssl.
|
338
|
-
|
339
440
|
Questions or problems?
|
340
441
|
----------------------
|
341
|
-
http://api.shopify.com <= Read up on the possible API calls!
|
342
|
-
|
343
|
-
http://ecommerce.shopify.com/c/shopify-apis-and-technology <= Ask questions!
|
344
442
|
|
345
|
-
|
443
|
+
- [Ask questions!](https://ecommerce.shopify.com/c/shopify-apis-and-technology)
|
444
|
+
- [Read the docs!](https://help.shopify.com/api/guides)
|
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg enable-background="new 0 0 1920 1080" version="1.1" viewBox="0 0 1920 1080" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><polygon points="1345 330.75 1345 437.24 1224.7 437.24 1224.7 676.56 873.52 676.56 874.04 643.85 1203.2 330.23" fill="#fff"/><path d="m1095.7 677.54c-18.553 0.074-37.107 0.163-55.66 0.126-18.553 0.056-37.107-0.188-55.66-0.233l-13.915-0.063-13.915 0.044-27.83 0.094c-18.553 0.128-37.107-5e-3 -55.66-0.056l-1.266-3e-3 3e-3 -1.259 0.047-22.532-0.093-22.532-0.068-11.266 6e-3 -11.266 0.019-22.532h2.703l0.111 22.532c0.053 7.511 0.06 15.022 0.038 22.532l-0.094 45.065-1.407-1.407c18.553 7e-3 37.107-0.041 55.66 0.086l27.83 0.131 13.915 0.066 13.915-0.028c18.553-8e-3 37.107-0.151 55.66-0.019 18.553 0.099 37.107 0.049 55.66-0.181v2.701z" fill="#0C1238"/><path d="m1225 677.54c-9.24 0.123-18.48 0.187-27.72 0.077l-13.86-0.213c-2.31-0.051-4.62-0.023-6.93 1e-3l-6.93 0.062c-9.24 0.156-18.48 0.076-27.72-0.054-2.31-0.034-4.62 1e-3 -6.93 2e-3l-6.93 0.121c-4.62 0.062-9.24-2e-3 -13.86 3e-3v-2.703c4.62-0.048 9.24-0.165 13.86-0.157l6.93 0.025c2.31 0.027 4.62 0.088 6.93 0.076 9.24-0.024 18.48-0.031 27.72 0.145 4.62 0.038 9.24 0.163 13.86 0.126l13.86-0.081c4.62-0.04 9.24 0.088 13.86 0.101 2.31 0.047 4.62-0.048 6.93-0.065 2.31-0.026 4.62-0.07 6.93-0.169v2.703z" fill="#0C1238"/><path d="m871.68 561.78l-0.13-115.72 0.07-115.72 1e-3 -1.414 1.411 3e-3 117.9 0.228 117.9-0.138 58.951-0.061 58.951 0.072 117.9 0.09 1.218 1e-3 4e-3 1.221 0.156 53.426-0.026 53.426h-2.703l-0.154-53.426 0.04-53.426 1.466 1.466-235.8-0.148-117.9-0.193-117.9 0.087 1.212-1.212-0.084 115.72c-0.058 19.286 0.032 38.573 0.074 57.859l0.15 57.859h-2.705z" fill="#0C1238"/><g fill="#E6E8F0"><circle cx="891.37" cy="344.49" r="6.812"/><circle cx="912.86" cy="345.01" r="6.812"/><circle cx="934.34" cy="345.54" r="6.812"/><path d="m1202.7 352.87h-186.64c-0.552 0-1-0.448-1-1v-11.624c0-0.552 0.448-1 1-1h186.64c0.552 0 1 0.448 1 1v11.624c0 0.552-0.448 1-1 1z" stroke="#F0F3F5" stroke-miterlimit="10"/><rect x="1288.6" y="339.25" width="17.816" height="13.624"/><path d="m1327.4 352.87h-15.816c-0.552 0-1-0.448-1-1v-11.624c0-0.552 0.448-1 1-1h15.816c0.552 0 1 0.448 1 1v11.624c0 0.552-0.447 1-1 1z"/></g><g fill="none" stroke="#8891EA" stroke-miterlimit="10" stroke-width="8"><path d="m1098.3 576.8c-24.295 0-43.99-19.695-43.99-43.99v-29.485c0-2.209 1.791-4 4-4h79.98c2.209 0 4 1.791 4 4v29.485c0 24.295-19.695 43.99-43.99 43.99z"/><path d="m1066 499.33v-12.41c0-17.804 14.433-32.237 32.237-32.237s32.237 14.433 32.237 32.237v12.41"/></g><circle cx="1098.3" cy="529.08" r="8.966" fill="#8891EA"/><line x1="1098.3" x2="1098.3" y1="529.08" y2="546.68" fill="#fff" stroke="#8891EA" stroke-linecap="round" stroke-miterlimit="10" stroke-width="8"/><polygon points="1416.1 676.19 1358 748.57 1416.1 749.77 1225 749.77 1225 659.42 1416.1 437.19" fill="#fff"/><path d="m1415.2 497.07l-0.12-59.83 1.472 1.472-95.89-0.052-47.945-0.135c-15.982-0.023-31.963-0.14-47.945-0.085l1.2-1.2 0.139 78.077c0.086 26.026 4e-3 52.052-0.039 78.077l-0.076 78.077c0.056 26.026 0.201 52.052 0.145 78.077l-1.368-1.368 38.25 0.017v2.703l-38.251 0.1-1.444 4e-3 -6e-3 -1.454c-0.102-26.026-0.045-52.052-0.026-78.077l0.068-78.077 0.067-78.077 0.191-78.077 3e-3 -1.15h1.147l47.945-0.013 47.945-0.051 95.89 0.089 1.121 1e-3 4e-3 1.125 0.226 59.83h-2.703z" fill="#0C1238"/><path d="m1417.9 518.33c0.051 19.268 0.165 38.536 0.128 57.804l-0.022 28.902-0.134 28.902-0.134 28.902 0.061 28.902 0.087 28.902 0.046 14.451-0.034 14.451-3e-3 1.353-1.347-3e-3c-22.64-0.042-45.28-0.192-67.919-0.118l-33.96 0.144-33.96-0.025v-2.703l33.96-0.143 33.96 0.01c11.32 0.049 22.64 0.1 33.96 0.078l33.96-2e-3 -1.409 1.409c-0.03-19.268 0.125-38.536 0.178-57.804l0.103-28.902-0.051-28.902-0.051-28.902 0.081-28.902c0.128-19.268-0.116-38.536-0.204-57.804h2.704z" fill="#0C1238"/><path d="m1400.3 458.72h-160.44c-0.552 0-1-0.448-1-1v-11.624c0-0.552 0.448-1 1-1h160.44c0.552 0 1 0.448 1 1v11.624c0 0.552-0.448 1-1 1z" fill="#E6E8F0" stroke="#F0F3F5" stroke-miterlimit="10"/><path d="m1238.5 467.44c13.587-0.084 27.173-0.121 40.76-0.055l20.38 0.141c6.793 0.061 13.587-0.03 20.38-0.038 13.587-0.116 27.173-0.022 40.76 0.038 6.793 0.029 13.587-0.022 20.38-0.082 6.793-0.046 13.587 0 20.38-5e-3v1.802c-13.587 0.111-27.173 0.144-40.76 0.036-13.587 2e-3 -27.173 0.027-40.76-0.09-6.793-0.025-13.587-0.117-20.38-0.088l-20.38 0.054c-6.793 0.022-13.587-0.048-20.38-0.067-6.793-7e-3 -13.587 0.107-20.38 0.154v-1.8z" fill="#E6E8F0"/><path d="m891.69 362.56c36.392-0.084 72.784-0.121 109.18-0.055l54.588 0.141c18.196 0.062 36.392-0.034 54.588-0.043l218.35-0.043v1.802c-36.392 0.111-72.784 0.144-109.18 0.036l-109.18-0.09-54.588-0.088-54.588 0.054-54.588-0.067-54.588 0.154v-1.801z" fill="#E6E8F0"/><g fill="none" stroke="#8891EA" stroke-miterlimit="10" stroke-width="6"><path d="m1320.6 638.41c-17.878 0-32.371-14.493-32.371-32.371v-21.697c0-1.626 1.318-2.943 2.943-2.943h58.854c1.626 0 2.943 1.318 2.943 2.943v21.697c1e-3 17.878-14.491 32.371-32.369 32.371z"/><path d="m1296.9 581.4v-9.132c0-13.101 10.62-23.722 23.722-23.722 13.101 0 23.722 10.621 23.722 23.722v9.132"/></g><circle cx="1320.6" cy="604.5" r="5.88" fill="#8891EA"/><line x1="1320.6" x2="1320.6" y1="603.3" y2="616.25" fill="#fff" stroke="#8891EA" stroke-linecap="round" stroke-miterlimit="10" stroke-width="6"/><path d="m966.35 697.36l-0.029 13.745c-0.01 1.145 0.011 2.291-0.023 3.436l-0.124 3.436c-0.103 2.291 0.022 4.582 0.121 6.872l-1.912-1.912c10.168-0.857 20.337-0.478 30.505-0.36 5.084 0.104 10.168 0.133 15.252 0.178 5.084 6e-3 10.168 0.199 15.252 0.287l7.626 0.168 7.626 0.264c2.542 0.09 5.084 0.032 7.626 0.023 2.542-0.035 5.084 0.047 7.626 0.065 10.168 0.377 20.337-0.052 30.505 0.201l7.626 0.04c2.542 6e-3 5.084-0.283 7.626-0.394 5.084-0.14 10.168-0.184 15.252-0.268 5.084-0.072 10.168-0.071 15.252-0.204 2.542-0.07 5.084-0.088 7.626-0.118 2.542-0.019 5.084 0.1 7.626 0.143 10.168 0.462 20.337-0.303 30.505 0.192 2.542 0.145 5.084 0.163 7.626 0.139 2.542 0 5.084-0.038 7.626-0.099l15.252-0.314v3.936l-15.252 0.106c-5.084 0.024-10.168 0.012-15.252 0.3-10.168 0.483-20.337-0.281-30.505-0.213-20.337-1.165-40.673 0.704-61.01-0.137-2.542 0.117-5.084 0.33-7.626 0.382-2.542 0.092-5.084 0.173-7.626-0.018s-5.084-0.219-7.626-0.183c-2.542-2e-3 -5.084 0.099-7.626 0.081-2.542-0.027-5.084 0.026-7.626-0.066-1.271-0.039-2.542-0.079-3.813-0.09-1.271-0.022-2.542-0.05-3.813 0.018-2.542 0.097-5.084 0.355-7.626 0.327-1.271-0.037-2.542-0.06-3.813-0.12l-3.813-0.238c-2.542-0.162-5.084-0.324-7.626-0.268-2.542 0.109-5.084-0.092-7.626-0.222-2.542-0.112-5.084-0.326-7.626-0.371-2.542-0.094-5.084-0.061-7.626-0.038-5.084 0.101-10.168 0.266-15.252 0.414-2.542 0.071-5.084 0.122-7.626 0.123l-7.626-0.19-1.598-0.04 0.032-1.527c0.047-2.291 0.153-4.582 9e-3 -6.872l-0.162-3.436c-0.047-1.145-0.04-2.291-0.062-3.436l-0.186-13.745h3.934z" fill="#E6E8F0"/><path d="m1434.8 722.88l16.096 0.019 8.048 0.01c2.683 0.018 5.365-0.029 8.048 0.05l-1.89 1.89c0.07-3.44 0.218-6.88 0.086-10.32l-0.312-10.32c-0.261-6.88-0.364-13.76-0.339-20.639l0.314-41.279c0.052-6.88 0.033-13.76 0.144-20.639l0.275-20.639c0.057-6.88 0.274-13.76 0.375-20.639 0.058-6.88-0.069-13.76 0.033-20.639l0.226-20.639-0.071-10.32-0.046-5.16 0.032-5.16 0.11-20.639c0.012-3.44 0.045-6.88-0.068-10.32-0.149-3.44-0.261-6.88-0.361-10.32l-0.328-41.279c-0.074-6.88-0.188-13.76-0.211-20.639 0.028-6.88 0.177-13.76 0.261-20.639l1.77 1.77c-4.37-0.095-8.74 1e-3 -13.111 1e-3l-13.111 0.063c-4.37 1e-3 -8.74 0.084-13.111 0.016l-13.111-0.231c-4.37-0.118-8.74-0.058-13.111-0.055-4.37-4e-3 -8.74 0.077-13.111 0.113l-26.221 0.29v-3.936l26.221-0.107 13.111-0.052c4.37-0.026 8.74 2e-3 13.111-0.14l13.111-0.262c4.37-0.066 8.74 0.04 13.111 0.051l26.221 0.283 2.211 0.024-0.016 2.172c-0.049 6.88-0.045 13.76-0.139 20.639-0.152 6.88-0.325 13.76-0.304 20.639l0.499 41.279c-0.024 1.72-0.037 3.44-0.138 5.16l-0.297 5.16c-0.137 3.44-0.045 6.88 0.01 10.32 0.12 6.88 0.479 13.76 0.59 20.639 0.273 6.88-0.127 13.76-0.227 20.639-0.014 6.88 0.146 13.76 0.091 20.639 0.051 6.88-0.202 13.76-0.162 20.639 0.04 3.44 0.226 6.88 0.324 10.32 0.061 3.44 4e-3 6.88-0.082 10.32l-0.356 10.32c-0.047 1.72-0.141 3.44-0.149 5.16l2e-3 5.16c-0.012 1.72 0.032 3.44-0.026 5.16l-0.164 5.16-0.335 10.32c-0.306 13.76 0.065 27.519 0.289 41.279 0.074 3.44 0.091 6.88 0.13 10.32 0.059 3.44-0.071 6.88-0.098 10.32l-0.153 10.32c-0.053 1.72 0.021 3.44 0.049 5.16l0.139 5.16 0.044 1.627-1.73 0.06c-2.683 0.093-5.365 0.065-8.048 0.1l-8.048 0.061-16.096 0.121v-3.941z" fill="#E6E8F0"/></svg>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
(function() {
|
2
|
+
function ITPHelper(opts) {
|
3
|
+
this.itpContent = document.getElementById('TopLevelInteractionContent');
|
4
|
+
this.itpAction = document.getElementById('TopLevelInteractionButton');
|
5
|
+
this.redirectUrl = opts.redirectUrl;
|
6
|
+
}
|
7
|
+
|
8
|
+
ITPHelper.prototype.redirect = function() {
|
9
|
+
sessionStorage.setItem('shopify.top_level_interaction', true);
|
10
|
+
window.location.href = this.redirectUrl;
|
11
|
+
}
|
12
|
+
|
13
|
+
ITPHelper.prototype.userAgentIsAffected = function() {
|
14
|
+
return Boolean(document.hasStorageAccess);
|
15
|
+
}
|
16
|
+
|
17
|
+
ITPHelper.prototype.canPartitionCookies = function() {
|
18
|
+
var versionRegEx = /Version\/12\.0\.?\d? Safari/;
|
19
|
+
return versionRegEx.test(navigator.userAgent);
|
20
|
+
}
|
21
|
+
|
22
|
+
ITPHelper.prototype.setUpContent = function(onClick) {
|
23
|
+
this.itpContent.style.display = 'block';
|
24
|
+
this.itpAction.addEventListener('click', this.redirect.bind(this));
|
25
|
+
}
|
26
|
+
|
27
|
+
ITPHelper.prototype.execute = function() {
|
28
|
+
if (!this.itpContent) {
|
29
|
+
return;
|
30
|
+
}
|
31
|
+
|
32
|
+
if (this.userAgentIsAffected()) {
|
33
|
+
this.setUpContent();
|
34
|
+
} else {
|
35
|
+
this.redirect();
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
this.ITPHelper = ITPHelper;
|
40
|
+
})(window);
|
@@ -0,0 +1,33 @@
|
|
1
|
+
(function() {
|
2
|
+
function redirect() {
|
3
|
+
var redirectTargetElement = document.getElementById("redirection-target");
|
4
|
+
|
5
|
+
if (!redirectTargetElement) {
|
6
|
+
return;
|
7
|
+
}
|
8
|
+
|
9
|
+
var targetInfo = JSON.parse(redirectTargetElement.dataset.target)
|
10
|
+
|
11
|
+
if (window.top == window.self) {
|
12
|
+
// If the current window is the 'parent', change the URL by setting location.href
|
13
|
+
window.top.location.href = targetInfo.url;
|
14
|
+
} else {
|
15
|
+
// If the current window is the 'child', change the parent's URL with postMessage
|
16
|
+
normalizedLink = document.createElement('a');
|
17
|
+
normalizedLink.href = targetInfo.url;
|
18
|
+
|
19
|
+
data = JSON.stringify({
|
20
|
+
message: 'Shopify.API.remoteRedirect',
|
21
|
+
data: {location: normalizedLink.href}
|
22
|
+
});
|
23
|
+
window.parent.postMessage(data, targetInfo.myshopifyUrl);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
document.addEventListener("DOMContentLoaded", redirect);
|
28
|
+
|
29
|
+
// In the turbolinks context, neither DOMContentLoaded nor turbolinks:load
|
30
|
+
// consistently fires. This ensures that we at least attempt to fire in the
|
31
|
+
// turbolinks situation as well.
|
32
|
+
redirect();
|
33
|
+
})();
|
@@ -0,0 +1,121 @@
|
|
1
|
+
(function() {
|
2
|
+
var ACCESS_GRANTED_STATUS = 'storage_access_granted';
|
3
|
+
var ACCESS_DENIED_STATUS = 'storage_access_denied';
|
4
|
+
|
5
|
+
function StorageAccessHelper(redirectData) {
|
6
|
+
this.redirectData = redirectData;
|
7
|
+
}
|
8
|
+
|
9
|
+
StorageAccessHelper.prototype.setNormalizedLink = function(storageAccessStatus) {
|
10
|
+
return storageAccessStatus === ACCESS_GRANTED_STATUS ? this.redirectData.hasStorageAccessUrl : this.redirectData.doesNotHaveStorageAccessUrl;
|
11
|
+
}
|
12
|
+
|
13
|
+
StorageAccessHelper.prototype.redirectToAppTLD = function(storageAccessStatus) {
|
14
|
+
var normalizedLink = document.createElement('a');
|
15
|
+
|
16
|
+
normalizedLink.href = this.setNormalizedLink(storageAccessStatus);
|
17
|
+
|
18
|
+
data = JSON.stringify({
|
19
|
+
message: 'Shopify.API.remoteRedirect',
|
20
|
+
data: {
|
21
|
+
location: normalizedLink.href,
|
22
|
+
}
|
23
|
+
});
|
24
|
+
window.parent.postMessage(data, this.redirectData.myshopifyUrl);
|
25
|
+
}
|
26
|
+
|
27
|
+
StorageAccessHelper.prototype.redirectToAppsIndex = function() {
|
28
|
+
window.parent.location.href = this.redirectData.myshopifyUrl + '/admin/apps';
|
29
|
+
}
|
30
|
+
|
31
|
+
StorageAccessHelper.prototype.redirectToAppHome = function() {
|
32
|
+
window.location.href = this.redirectData.appHomeUrl;
|
33
|
+
}
|
34
|
+
|
35
|
+
StorageAccessHelper.prototype.grantedStorageAccess = function() {
|
36
|
+
try {
|
37
|
+
sessionStorage.setItem('shopify.granted_storage_access', true);
|
38
|
+
document.cookie = 'shopify.granted_storage_access=true';
|
39
|
+
this.redirectToAppHome();
|
40
|
+
} catch (error) {
|
41
|
+
console.warn('Third party cookies may be blocked.', error);
|
42
|
+
this.redirectToAppTLD(ACCESS_DENIED_STATUS);
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
StorageAccessHelper.prototype.handleRequestStorageAccess = function() {
|
47
|
+
return document.requestStorageAccess().then(this.grantedStorageAccess.bind(this), this.redirectToAppsIndex.bind(this, ACCESS_DENIED_STATUS));
|
48
|
+
}
|
49
|
+
|
50
|
+
StorageAccessHelper.prototype.setupRequestStorageAccess = function() {
|
51
|
+
var requestContent = document.getElementById('RequestStorageAccess');
|
52
|
+
var requestButton = document.getElementById('TriggerAllowCookiesPrompt');
|
53
|
+
|
54
|
+
requestButton.addEventListener('click', this.handleRequestStorageAccess.bind(this));
|
55
|
+
requestContent.style.display = 'block';
|
56
|
+
}
|
57
|
+
|
58
|
+
StorageAccessHelper.prototype.handleHasStorageAccess = function() {
|
59
|
+
if (sessionStorage.getItem('shopify.granted_storage_access')) {
|
60
|
+
// If app was classified by ITP and used Storage Access API to acquire access
|
61
|
+
this.redirectToAppHome();
|
62
|
+
} else {
|
63
|
+
// If app has not been classified by ITP and still has storage access
|
64
|
+
this.redirectToAppTLD(ACCESS_GRANTED_STATUS);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
StorageAccessHelper.prototype.handleGetStorageAccess = function() {
|
69
|
+
if (sessionStorage.getItem('shopify.top_level_interaction')) {
|
70
|
+
// If merchant has been redirected to interact with TLD (requirement for prompting request to gain storage access)
|
71
|
+
this.setupRequestStorageAccess();
|
72
|
+
} else {
|
73
|
+
// If merchant has not been redirected to interact with TLD (requirement for prompting request to gain storage access)
|
74
|
+
this.redirectToAppTLD(ACCESS_DENIED_STATUS);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
StorageAccessHelper.prototype.manageStorageAccess = function() {
|
79
|
+
return document.hasStorageAccess().then(function(hasAccess) {
|
80
|
+
if (hasAccess) {
|
81
|
+
this.handleHasStorageAccess();
|
82
|
+
} else {
|
83
|
+
this.handleGetStorageAccess();
|
84
|
+
}
|
85
|
+
}.bind(this));
|
86
|
+
}
|
87
|
+
|
88
|
+
StorageAccessHelper.prototype.execute = function() {
|
89
|
+
if (ITPHelper.prototype.canPartitionCookies()) {
|
90
|
+
this.setUpCookiePartitioning();
|
91
|
+
return;
|
92
|
+
}
|
93
|
+
|
94
|
+
if (ITPHelper.prototype.userAgentIsAffected()) {
|
95
|
+
this.manageStorageAccess();
|
96
|
+
} else {
|
97
|
+
this.grantedStorageAccess();
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
/* ITP 2.0 solution: handles cookie partitioning */
|
102
|
+
StorageAccessHelper.prototype.setUpHelper = function() {
|
103
|
+
return new ITPHelper({redirectUrl: window.shopOrigin + "/admin/apps/" + window.apiKey});
|
104
|
+
}
|
105
|
+
|
106
|
+
StorageAccessHelper.prototype.setCookieAndRedirect = function() {
|
107
|
+
document.cookie = "shopify.cookies_persist=true";
|
108
|
+
var helper = this.setUpHelper();
|
109
|
+
helper.redirect();
|
110
|
+
}
|
111
|
+
|
112
|
+
StorageAccessHelper.prototype.setUpCookiePartitioning = function() {
|
113
|
+
var itpContent = document.getElementById('CookiePartitionPrompt');
|
114
|
+
itpContent.style.display = 'block';
|
115
|
+
|
116
|
+
var button = document.getElementById('AcceptCookies');
|
117
|
+
button.addEventListener('click', this.setCookieAndRedirect.bind(this));
|
118
|
+
}
|
119
|
+
|
120
|
+
this.StorageAccessHelper = StorageAccessHelper;
|
121
|
+
})(window);
|