shopify_app 9.0.0 → 13.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -2
- data/CHANGELOG.md +159 -0
- data/README.md +221 -130
- data/app/assets/javascripts/shopify_app/itp_helper.js +6 -6
- data/app/assets/javascripts/shopify_app/storage_access.js +36 -7
- data/app/controllers/concerns/shopify_app/authenticated.rb +2 -2
- data/app/controllers/shopify_app/callback_controller.rb +20 -7
- data/app/controllers/shopify_app/extension_verification_controller.rb +20 -0
- data/app/controllers/shopify_app/sessions_controller.rb +71 -22
- data/app/views/shopify_app/sessions/enable_cookies.html.erb +3 -1
- data/app/views/shopify_app/sessions/request_storage_access.html.erb +2 -1
- data/app/views/shopify_app/sessions/top_level_interaction.html.erb +1 -0
- data/app/views/shopify_app/shared/redirect.html.erb +1 -0
- data/config/locales/cs.yml +23 -0
- data/config/locales/da.yml +20 -0
- data/config/locales/de.yml +5 -5
- data/config/locales/en.yml +1 -1
- data/config/locales/es.yml +3 -3
- data/config/locales/fi.yml +20 -0
- data/config/locales/fr.yml +3 -3
- data/config/locales/hi.yml +23 -0
- data/config/locales/it.yml +2 -3
- data/config/locales/ja.yml +1 -1
- data/config/locales/ko.yml +19 -0
- data/config/locales/ms.yml +22 -0
- data/config/locales/nb.yml +21 -0
- data/config/locales/nl.yml +3 -3
- data/config/locales/pl.yml +21 -0
- data/config/locales/pt-BR.yml +9 -10
- data/config/locales/pt-PT.yml +22 -0
- data/config/locales/sv.yml +21 -0
- data/config/locales/th.yml +20 -0
- data/config/locales/tr.yml +22 -0
- data/config/locales/zh-CN.yml +3 -3
- data/config/locales/zh-TW.yml +1 -2
- data/docs/Quickstart.md +44 -16
- data/docs/Releasing.md +0 -1
- data/docs/install-on-dev-shop.png +0 -0
- data/docs/test-your-app.png +0 -0
- data/karma.conf.js +1 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +2 -2
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +39 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +62 -0
- data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +0 -6
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
- data/lib/generators/shopify_app/install/install_generator.rb +27 -11
- data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +1 -17
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +11 -10
- data/lib/generators/shopify_app/install/templates/flash_messages.js +26 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.js +15 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.rb +6 -3
- data/lib/generators/shopify_app/install/templates/shopify_app_index.js +2 -0
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -0
- data/lib/generators/shopify_app/install/templates/user_agent.rb +5 -0
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +1 -1
- data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -1
- data/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +16 -0
- data/lib/generators/shopify_app/user_model/templates/user.rb +7 -0
- data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +38 -0
- data/lib/shopify_app/configuration.rb +22 -10
- data/lib/shopify_app/controller_concerns/login_protection.rb +66 -15
- data/lib/shopify_app/engine.rb +4 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +1 -1
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +33 -0
- data/lib/shopify_app/session/in_memory_session_store.rb +1 -1
- data/lib/shopify_app/session/in_memory_shop_session_store.rb +4 -0
- data/lib/shopify_app/session/in_memory_user_session_store.rb +4 -0
- data/lib/shopify_app/session/session_repository.rb +38 -13
- data/lib/shopify_app/session/session_storage.rb +0 -22
- data/lib/shopify_app/session/shop_session_storage.rb +30 -0
- data/lib/shopify_app/session/user_session_storage.rb +31 -0
- data/lib/shopify_app/utils.rb +7 -0
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +48 -29
- data/package-lock.json +7227 -5
- data/package.json +9 -9
- data/service.yml +1 -1
- data/shopify_app.gemspec +10 -5
- data/translation.yml +1 -1
- data/yarn.lock +2555 -1886
- metadata +117 -19
- data/lib/generators/shopify_app/home_controller/templates/shopify_app_ready_script.html.erb +0 -7
data/README.md
CHANGED
@@ -12,73 +12,45 @@ Shopify Application Rails engine and generator
|
|
12
12
|
|
13
13
|
Table of Contents
|
14
14
|
-----------------
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
* [**Authenticated**](#authenticated)
|
35
|
-
* [**AppProxyVerification**](#appproxyverification)
|
36
|
-
* [Recommended Usage](#recommended-usage)
|
37
|
-
* [**Upgrading from 8.6 to 9.0.0**](#upgrading-from-86-to-900)
|
38
|
-
* [**Troubleshooting**](#troubleshooting)
|
39
|
-
* [Generator shopify_app:install hangs](#generator-shopify_appinstall-hangs)
|
40
|
-
* [**Testing an embedded app outside the Shopify admin**](#testing-an-embedded-app-outside-the-shopify-admin)
|
41
|
-
* [**Questions or problems?**](#questions-or-problems)
|
42
|
-
|
43
|
-
|
44
|
-
Description
|
15
|
+
- [Introduction](#introduction)
|
16
|
+
- [Become a Shopify App Developer](#become-a-shopify-app-developer)
|
17
|
+
- [Installation](#installation)
|
18
|
+
- [Generators](#generators)
|
19
|
+
- [Mounting the Engine](#mounting-the-engine)
|
20
|
+
- [Authentication](#authentication)
|
21
|
+
- [WebhooksManager](#webhooksmanager)
|
22
|
+
- [ScripttagsManager](#scripttagsmanager)
|
23
|
+
- [RotateShopifyTokenJob](#rotateshopifytokenjob)
|
24
|
+
- [App Tunneling](#app-tunneling)
|
25
|
+
- [AppProxyVerification](#appproxyverification)
|
26
|
+
- [Troubleshooting](#troubleshooting)
|
27
|
+
- [Testing an embedded app outside the Shopify admin](#testing-an-embedded-app-outside-the-shopify-admin)
|
28
|
+
- [Migration to 13.0.0](#migrating-to-13)
|
29
|
+
- [Questions or problems?](#questions-or-problems-)
|
30
|
+
- [Rails 6 Compatibility](#rails-6-compatibility)
|
31
|
+
- [Upgrading from 8.6 to 9.0.0](#upgrading-from-86-to-900)
|
32
|
+
|
33
|
+
Introduction
|
45
34
|
-----------
|
46
|
-
This gem includes a Rails Engine and generators for writing Rails applications using the Shopify API. The Engine provides a SessionsController and all the required code for authenticating with a shop via Oauth (other authentication methods are not supported).
|
35
|
+
Get started with the [Shopify Admin API](https://help.shopify.com/en/api/getting-started) faster; This gem includes a Rails Engine and generators for writing Rails applications using the Shopify API. The Engine provides a SessionsController and all the required code for authenticating with a shop via Oauth (other authentication methods are not supported).
|
47
36
|
|
48
|
-
|
37
|
+
*Note: It's recommended to use this on a new Rails project, so that the generator won't overwrite/delete your files.*
|
49
38
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
Quickstart
|
54
|
-
----------
|
55
|
-
|
56
|
-
Check out this screencast on how to create and deploy a new Shopify App to Heroku in 5 minutes:
|
39
|
+
Learn how to create and deploy a new Shopify App to Heroku with our [quickstart guide](https://github.com/Shopify/shopify_app/blob/master/docs/Quickstart.md), or dive in in less than 5 minutes with this quickstart video:
|
57
40
|
|
58
41
|
[https://www.youtube.com/watch?v=yGxeoAHlQOg](https://www.youtube.com/watch?v=yGxeoAHlQOg)
|
59
42
|
|
60
|
-
|
61
|
-
|
62
|
-
App Tunneling
|
63
|
-
-------------
|
64
|
-
|
65
|
-
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.
|
66
|
-
|
67
|
-
For example with [ngrok](https://ngrok.com/), run this command to set up proxying to Rails' default port:
|
68
|
-
|
69
|
-
```sh
|
70
|
-
ngrok http 3000
|
71
|
-
```
|
72
|
-
|
73
|
-
Becoming a Shopify App Developer
|
43
|
+
Become a Shopify App Developer
|
74
44
|
--------------------------------
|
75
|
-
If you don't have a Shopify Partner account
|
45
|
+
To become a Shopify App Developer you'll need a [Shopify Partner account.](http://shopify.com/partners) If you don't have a Shopify Partner account, head to http://shopify.com/partners to create one before you start.
|
76
46
|
|
77
|
-
Once you have a Partner account create a new application
|
47
|
+
Once you have a Partner account, [create a new application in the Partner Dashboard](https://help.shopify.com/en/api/tools/partner-dashboard/your-apps) to get an API key and other API credentials.
|
48
|
+
|
49
|
+
To create an application for development set your new app's `App URL` to the URL provided by [your tunnel](#app-tunneling), ensuring that you use `https://`. If you are not planning to embed your app inside the Shopify admin or receive webhooks, set your redirect URL to `http://localhost:3000/` and the `Whitelisted redirection URL(s)` to contain `<App URL>/auth/shopify/callback`.
|
78
50
|
|
79
51
|
Installation
|
80
52
|
------------
|
81
|
-
To get started add shopify_app to your Gemfile and bundle install
|
53
|
+
To get started add `shopify_app` to your Gemfile and run `bundle install`:
|
82
54
|
|
83
55
|
``` sh
|
84
56
|
# Create a new rails app
|
@@ -90,7 +62,7 @@ $ echo "gem 'shopify_app'" >> Gemfile
|
|
90
62
|
$ bundle install
|
91
63
|
```
|
92
64
|
|
93
|
-
Now we are ready to run any of the shopify_app
|
65
|
+
Now we are ready to run any of the [generators](#generators) included with `shopify_app`. The following section explains the generators and what you can do with them.
|
94
66
|
|
95
67
|
|
96
68
|
#### Rails Compatibility
|
@@ -103,13 +75,26 @@ Generators
|
|
103
75
|
|
104
76
|
### Default Generator
|
105
77
|
|
106
|
-
The default generator will run the `install`, `shop`, and `home_controller` generators. This is the recommended way to start
|
78
|
+
The default generator will run the `install`, `shop`, and `home_controller` generators. This is the recommended way to start a new app from scratch:
|
107
79
|
|
108
80
|
```sh
|
109
|
-
$ rails generate shopify_app
|
81
|
+
$ rails generate shopify_app
|
82
|
+
```
|
83
|
+
|
84
|
+
After running the generator, you will need to run `rails db:migrate` to add new tables to your database. You can start your app with `bundle exec rails server` and install your app by visiting `http://localhost` in your web browser.
|
85
|
+
|
86
|
+
### API Keys
|
87
|
+
|
88
|
+
The default and install generators have been updated to source Shopify API key and secret from an Environment (`.env`) variables file, which you will need to create with the following format:
|
89
|
+
|
90
|
+
```
|
91
|
+
SHOPIFY_API_KEY=your api key
|
92
|
+
SHOPIFY_API_SECRET=your api secret
|
110
93
|
```
|
111
94
|
|
112
|
-
|
95
|
+
These values can be found on the "App Setup" page in the [Shopify Partners Dashboard][dashboard]. If you are checking your code into a code repository, ensure your `.gitignore` prevents your `.env` file from being checked into any publicly accessible code.
|
96
|
+
|
97
|
+
**You will need to load the ENV variables into your enviroment, you can do this with the [dot-env](https://github.com/bkeepers/dotenv) gem or any other method you wish to.**
|
113
98
|
|
114
99
|
### Install Generator
|
115
100
|
|
@@ -118,7 +103,7 @@ $ rails generate shopify_app:install
|
|
118
103
|
|
119
104
|
# or optionally with arguments:
|
120
105
|
|
121
|
-
$ rails generate shopify_app:install
|
106
|
+
$ rails generate shopify_app:install
|
122
107
|
```
|
123
108
|
|
124
109
|
Other options include:
|
@@ -135,24 +120,13 @@ The generator adds ShopifyApp and the required initializers to the host Rails ap
|
|
135
120
|
After running the `install` generator, you can start your app with `bundle exec rails server` and install your app by visiting localhost.
|
136
121
|
|
137
122
|
|
138
|
-
### Shop Model Generator
|
139
|
-
|
140
|
-
```sh
|
141
|
-
$ rails generate shopify_app:shop_model
|
142
|
-
```
|
143
|
-
|
144
|
-
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.
|
145
|
-
|
146
|
-
*Note that you will need to run rake db:migrate after this generator*
|
147
|
-
|
148
|
-
|
149
123
|
### Home Controller Generator
|
150
124
|
|
151
125
|
```sh
|
152
126
|
$ rails generate shopify_app:home_controller
|
153
127
|
```
|
154
128
|
|
155
|
-
This generator creates an example home controller and view which fetches and displays products using the
|
129
|
+
This generator creates an example home controller and view which fetches and displays products using the Shopify API
|
156
130
|
|
157
131
|
|
158
132
|
### App Proxy Controller Generator
|
@@ -163,6 +137,13 @@ $ rails generate shopify_app:app_proxy_controller
|
|
163
137
|
|
164
138
|
This optional generator, not included with the default generator, creates the app proxy controller to handle proxy requests to the app from your shop storefront, modifies 'config/routes.rb' with a namespace route, and an example view which displays current shop information using the LiquidAPI
|
165
139
|
|
140
|
+
### Marketing Extension Generator
|
141
|
+
|
142
|
+
```sh
|
143
|
+
$ rails generate shopify_app:add_marketing_activity_extension
|
144
|
+
```
|
145
|
+
|
146
|
+
This will create a controller with the endpoints required to build a [marketing activities extension](https://help.shopify.com/en/api/embedded-apps/app-extensions/shopify-admin/marketing-activities). The extension will be generated with a base url at `/marketing_activities`, which should also be configured in partners.
|
166
147
|
|
167
148
|
### Controllers, Routes and Views
|
168
149
|
|
@@ -207,6 +188,19 @@ provider :shopify,
|
|
207
188
|
callback_path: '/nested/auth/shopify/callback'
|
208
189
|
```
|
209
190
|
|
191
|
+
You may also need to change your `config/routes.rb` to render a view for `/nested`, since this is what will be rendered in the Shopify Admin of any shops that have installed your app. The engine itself doesn't have a view for this, so you'll need something like this:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# config/routes.rb
|
195
|
+
Rails.application.routes.draw do
|
196
|
+
root :to => 'something_else#index'
|
197
|
+
get "/nested", to: "home#index"
|
198
|
+
mount ShopifyApp::Engine, at: '/nested'
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
Finally, note that if you do this, to add your app to a store, you must navigate to `/nested` in order to render the `Enter your shop domain to log in or install this app.` UI.
|
203
|
+
|
210
204
|
### Custom login URL
|
211
205
|
|
212
206
|
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.:
|
@@ -217,37 +211,105 @@ ShopifyApp.configure do |config|
|
|
217
211
|
end
|
218
212
|
```
|
219
213
|
|
220
|
-
|
221
|
-
|
222
|
-
|
214
|
+
Authentication
|
215
|
+
--------------
|
216
|
+
|
217
|
+
### Callback
|
218
|
+
|
219
|
+
Upon completing the authentication flow Shopify calls the app at the `callback_path` mentioned before. If the app needs to do some extra work it can define and configure the route to a custom callback controller, inheriting from `ShopifyApp::CallbackController` and hook into or override any of the defined helper methods. The default callback controller already provides the following behaviour:
|
220
|
+
* Logging into the shop and resetting the session
|
221
|
+
* [Installing Webhooks](https://github.com/Shopify/shopify_app#webhooksmanager)
|
222
|
+
* [Setting Scripttags](https://github.com/Shopify/shopify_app#scripttagsmanager)
|
223
|
+
* [Performing the AfterAuthenticate Job](https://github.com/Shopify/shopify_app#afterauthenticatejob)
|
224
|
+
* Redirecting to the return address
|
225
|
+
|
226
|
+
**Note that starting with version 8.4.0, we have extracted the callback logic in its own controller. If you are upgrading from a version older than 8.4.0 the callback action and related helper methods were defined in `ShopifyApp::SessionsController` ==> you will have to extend `ShopifyApp::CallbackController` instead and port your logic to the new controller.**
|
227
|
+
|
228
|
+
### ShopifyApp::SessionRepository
|
229
|
+
|
230
|
+
`ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are stored and retrieved 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(auth_session, *args)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. These methods are already implemented as part of the `ShopifyApp::SessionStorage` concern, but can be overridden for custom implementation.
|
231
|
+
|
232
|
+
#### Shop-based token storage
|
233
|
+
Storing tokens on the store model means that any user login associated to the store will have equal access levels to whatever the original user granted the app.
|
234
|
+
```sh
|
235
|
+
$ rails generate shopify_app:shop_model
|
236
|
+
```
|
237
|
+
This will generate a shop model which will be the storage for the tokens necessary for authentication.
|
238
|
+
|
239
|
+
#### User-based token storage
|
240
|
+
A more granular control over level of access per user on an app might be necessary, to which the shop-based token strategy is not sufficient. Shopify supports a user-based token storage strategy where a unique token to each user can be managed. Shop tokens must still be maintained if you are running background jobs so that you can make use of them when necessary.
|
241
|
+
```sh
|
242
|
+
$ rails generate shopify_app:shop_model
|
243
|
+
$ rails generate shopify_app:user_model
|
244
|
+
```
|
245
|
+
This will generate a shop model and user model which will be the storage for the tokens necessary for authentication.
|
223
246
|
|
247
|
+
The current Shopify user will be stored in the rails session at `session[:shopify_user]`
|
248
|
+
|
249
|
+
Read more about Online vs. Offline access [here](https://help.shopify.com/api/getting-started/authentication/oauth).
|
250
|
+
|
251
|
+
#### Migrating from shop-based to user-based token strategy
|
252
|
+
1. Run the `user_model` generator as mentioned above.
|
253
|
+
2. Ensure that both your `Shop` model and `User` model includes the necessary concerns `ShopifyApp::ShopSessionStorage` and `ShopifyApp::UserSessionStorage`.
|
254
|
+
3. Make changes to 2 initializer files as shown below:
|
224
255
|
```ruby
|
256
|
+
# In the `omniauth.rb` initializer:
|
225
257
|
provider :shopify,
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
258
|
+
...
|
259
|
+
setup: lambda { |env|
|
260
|
+
...
|
261
|
+
# Add this line
|
262
|
+
strategy.options[:per_user_permissions] = strategy.session[:user_tokens]
|
263
|
+
...
|
264
|
+
}
|
265
|
+
|
266
|
+
# In the `shopify_app.rb` initializer:
|
267
|
+
config.shop_session_repository = {YOUR_SHOP_MODEL_CLASS}
|
268
|
+
config.user_session_repository = {YOUR_USER_MODEL_CLASS}
|
230
269
|
```
|
231
270
|
|
232
|
-
|
271
|
+
### Authenticated
|
233
272
|
|
234
|
-
|
273
|
+
The engine provides a `ShopifyApp::Authenticated` concern which should be included in any controller that is intended to be behind Shopify OAuth. It adds `before_action`s to ensure that the user is authenticated and will redirect to the Shopify login page if not. It is best practice to include this concern in a base controller inheriting from your `ApplicationController`, from which all controllers that require Shopify authentication inherit.
|
235
274
|
|
236
|
-
|
237
|
-
-----------------
|
275
|
+
For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`.
|
238
276
|
|
239
|
-
|
277
|
+
### AfterAuthenticate Job
|
278
|
+
|
279
|
+
If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), 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:
|
240
280
|
|
241
281
|
```ruby
|
242
282
|
ShopifyApp.configure do |config|
|
243
|
-
config.
|
244
|
-
config.api_key = ENV['SHOPIFY_CLIENT_API_KEY']
|
245
|
-
config.secret = ENV['SHOPIFY_CLIENT_API_SECRET']
|
246
|
-
config.scope = 'read_customers, write_products'
|
247
|
-
config.embedded_app = true
|
283
|
+
config.after_authenticate_job = { job: "Shopify::AfterAuthenticateJob" }
|
248
284
|
end
|
249
285
|
```
|
250
286
|
|
287
|
+
The job can be configured as either a class or a class name string.
|
288
|
+
|
289
|
+
If you need the job to run synchronously add the `inline` flag:
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
ShopifyApp.configure do |config|
|
293
|
+
config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true }
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
We've also provided a generator which creates a skeleton job and updates the initializer for you:
|
298
|
+
|
299
|
+
```
|
300
|
+
bin/rails g shopify_app:add_after_authenticate_job
|
301
|
+
```
|
302
|
+
|
303
|
+
If you want to perform that action only once, e.g. send a welcome email to the user when they install the app, you should make sure that this action is idempotent, meaning that it won't have an impact if run multiple times.
|
304
|
+
|
305
|
+
API Versioning
|
306
|
+
--------------
|
307
|
+
|
308
|
+
Shopify's API is versioned, and you can [read about that process in the Shopify Developers documentation page](https://shopify.dev/concepts/about-apis/versioning).
|
309
|
+
|
310
|
+
Since shopify_app gem version 1.11.0, the included shopify_api gem has also been updated to allow you to easily set and switch what version of the Shopify API you want your app or service to use, as well as surface warnings to Rails apps about [deprecated endpoints, GraphQL fields and more](https://shopify.dev/concepts/about-apis/versioning#deprecation-practices).
|
311
|
+
|
312
|
+
See the [shopify_api gem README](https://github.com/Shopify/shopify_api/) for more details.
|
251
313
|
|
252
314
|
WebhooksManager
|
253
315
|
---------------
|
@@ -284,7 +346,7 @@ ShopifyApp.configure do |config|
|
|
284
346
|
end
|
285
347
|
```
|
286
348
|
|
287
|
-
If you'd rather implement your own controller then you'll want to use the
|
349
|
+
If you'd rather implement your own controller then you'll want to use the WebhookVerification module to verify your webhooks, example:
|
288
350
|
|
289
351
|
```ruby
|
290
352
|
class CustomWebhooksController < ApplicationController
|
@@ -340,31 +402,6 @@ Scripttags are created in the same way as the Webhooks, with a background job wh
|
|
340
402
|
|
341
403
|
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.
|
342
404
|
|
343
|
-
AfterAuthenticate Job
|
344
|
-
---------------------
|
345
|
-
|
346
|
-
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:
|
347
|
-
|
348
|
-
```ruby
|
349
|
-
ShopifyApp.configure do |config|
|
350
|
-
config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob }
|
351
|
-
end
|
352
|
-
```
|
353
|
-
|
354
|
-
If you need the job to run synchronously add the `inline` flag:
|
355
|
-
|
356
|
-
```ruby
|
357
|
-
ShopifyApp.configure do |config|
|
358
|
-
config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true }
|
359
|
-
end
|
360
|
-
```
|
361
|
-
|
362
|
-
We've also provided a generator which creates a skeleton job and updates the initializer for you:
|
363
|
-
|
364
|
-
```
|
365
|
-
bin/rails g shopify_app:add_after_authenticate_job
|
366
|
-
```
|
367
|
-
|
368
405
|
RotateShopifyTokenJob
|
369
406
|
---------------------
|
370
407
|
|
@@ -391,19 +428,18 @@ The generated rake task will be found at `lib/tasks/shopify/rotate_shopify_token
|
|
391
428
|
strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
|
392
429
|
```
|
393
430
|
|
394
|
-
|
395
|
-
|
431
|
+
App Tunneling
|
432
|
+
-------------
|
396
433
|
|
397
|
-
|
434
|
+
Your local app needs to be accessible from the public Internet in order to install it on a Shopify store, to use the [App Proxy Controller](#app-proxy-controller-generator) or receive Webhooks.
|
398
435
|
|
399
|
-
|
436
|
+
Use a tunneling service like [ngrok](https://ngrok.com/), [Forward](https://forwardhq.com/), [Beeceptor](https://beeceptor.com/), [Mockbin](http://mockbin.org/), or [Hookbin](https://hookbin.com/) to make your development environment accessible to the internet.
|
400
437
|
|
401
|
-
|
402
|
-
-------------
|
438
|
+
For example with [ngrok](https://ngrok.com/), run this command to set up a tunnel proxy to Rails' default port:
|
403
439
|
|
404
|
-
|
405
|
-
|
406
|
-
|
440
|
+
```sh
|
441
|
+
ngrok http 3000
|
442
|
+
```
|
407
443
|
|
408
444
|
AppProxyVerification
|
409
445
|
--------------------
|
@@ -423,9 +459,14 @@ class ReviewsController < ApplicationController
|
|
423
459
|
end
|
424
460
|
```
|
425
461
|
|
426
|
-
Create your app proxy url in the [Shopify Partners' Dashboard]
|
462
|
+
Create your app proxy url in the [Shopify Partners' Dashboard][dashboard], making sure to point it to `https://your_app_website.com/app_proxy`.
|
427
463
|
![Creating an App Proxy](/images/app-proxy-screenshot.png)
|
428
464
|
|
465
|
+
App Bridge
|
466
|
+
---
|
467
|
+
|
468
|
+
A basic example of using [App Bridge][app-bridge] is included in the install generator. An app instance is automatically initialized in [shopify_app.js](https://github.com/Shopify/shopify_app/blob/master/lib/generators/shopify_app/install/templates/shopify_app.js) and [flash_messages.js](https://github.com/Shopify/shopify_app/blob/master/lib/generators/shopify_app/install/templates/flash_messages.js) converts Rails [flash messages](https://api.rubyonrails.org/classes/ActionDispatch/Flash.html) to App Bridge Toast actions automatically. By default, this library is included via [unpkg in the embedded_app layout](https://github.com/Shopify/shopify_app/blob/master/lib/generators/shopify_app/install/templates/embedded_app.html.erb#L27). For more advanced uses it is recommended to [install App Bridge via npm or yarn](https://help.shopify.com/en/api/embedded-apps/app-bridge/getting-started#set-up-shopify-app-bridge-in-your-app).
|
469
|
+
|
429
470
|
Troubleshooting
|
430
471
|
---------------
|
431
472
|
|
@@ -440,12 +481,59 @@ By default, loading your embedded app will redirect to the Shopify admin, with t
|
|
440
481
|
forceRedirect: <%= Rails.env.development? || Rails.env.test? ? 'false' : 'true' %>
|
441
482
|
```
|
442
483
|
|
484
|
+
Migrating to 13.0.0
|
485
|
+
-------------------
|
486
|
+
|
487
|
+
Version 13.0.0 adds the ability to use both user and shop sessions, concurrently. This however involved a large
|
488
|
+
change to how session stores work. Here are the steps to migrate to 13.x
|
489
|
+
|
490
|
+
### Changes to `config/initializers/shopify_app.rb`
|
491
|
+
- *REMOVE* `config.per_user_tokens = [true|false]` this is no longer needed
|
492
|
+
- *CHANGE* `config.session_repository = 'Shop'` To `config.shop_session_repository = 'Shop'`
|
493
|
+
- *ADD (optional)* User Session Storage `config.user_session_repository = 'User'`
|
494
|
+
|
495
|
+
### Shop Model Changes (normally `app/models/shop.rb`)
|
496
|
+
- *CHANGE* `include ShopifyApp::SessionStorage` to `include ShopifyApp::ShopSessionStorage`
|
497
|
+
|
498
|
+
### Changes to `ShopifyApp::LoginProtection`
|
499
|
+
`ShopifyApp::LoginProtection`
|
500
|
+
|
501
|
+
if you are using `ShopifyApp::LoginProtection#shop_session` in your code, it will need to be
|
502
|
+
changed to `ShopifyApp::LoginProtection#activate_shopify_session`
|
503
|
+
|
504
|
+
### Notes
|
505
|
+
You do not need a user model, a shop session is fine for most applications.
|
506
|
+
|
443
507
|
Questions or problems?
|
444
508
|
----------------------
|
445
509
|
|
446
510
|
- [Ask questions!](https://ecommerce.shopify.com/c/shopify-apis-and-technology)
|
447
511
|
- [Read the docs!](https://help.shopify.com/api/guides)
|
512
|
+
- And don't forget to check the [Changelog](https://github.com/Shopify/shopify_app/blob/master/CHANGELOG.md) too!
|
448
513
|
|
514
|
+
Upgrading to 11.7.0
|
515
|
+
---------------------------
|
516
|
+
|
517
|
+
### Session storage method signature breaking change
|
518
|
+
If you override `def self.store(auth_session)` method in your session storage model (e.g. Shop), the method signature has changed to `def self.store(auth_session, *args)` in order to support user-based token storage. Please update your method signature to include the second argument.
|
519
|
+
|
520
|
+
Rails 6 Compatibility
|
521
|
+
---------------------
|
522
|
+
|
523
|
+
### Disable Webpacker
|
524
|
+
If you are using sprockets in rails 6 or want to generate a shopify_app without webpacker run the install task by running
|
525
|
+
|
526
|
+
```
|
527
|
+
SHOPIFY_APP_DISABLE_WEBPACKER=1 rails generate shopify_app
|
528
|
+
```
|
529
|
+
|
530
|
+
and then in your ShopifyApp configuration block, add
|
531
|
+
|
532
|
+
```
|
533
|
+
ShopifyApp.configure do |config|
|
534
|
+
config.disable_webpacker = true
|
535
|
+
end
|
536
|
+
```
|
449
537
|
|
450
538
|
Upgrading from 8.6 to 9.0.0
|
451
539
|
---------------------------
|
@@ -453,14 +541,14 @@ Upgrading from 8.6 to 9.0.0
|
|
453
541
|
### Configuration change
|
454
542
|
|
455
543
|
Add an api version configuration in `config/initializers/shopify_app.rb`
|
456
|
-
Set this to the version you want to run against by default
|
544
|
+
Set this to the version you want to run against by default. See [Shopify API docs](https://help.shopify.com/en/api/versioning) for versions available.
|
457
545
|
```ruby
|
458
546
|
config.api_version = '2019-04'
|
459
547
|
```
|
460
548
|
|
461
549
|
### Session storage change
|
462
550
|
|
463
|
-
You will need to add an `api_version` method to you session storage object. The default
|
551
|
+
You will need to add an `api_version` method to you session storage object. The default implementation for this is.
|
464
552
|
```ruby
|
465
553
|
def api_version
|
466
554
|
ShopifyApp.configuration.api_version
|
@@ -469,7 +557,7 @@ end
|
|
469
557
|
|
470
558
|
### Generated file change
|
471
559
|
|
472
|
-
`embedded_app.html.erb` the
|
560
|
+
`embedded_app.html.erb` the usage of `shop_session.url` needs to be changed to `shop_session.domain`
|
473
561
|
```erb
|
474
562
|
<script type="text/javascript">
|
475
563
|
ShopifyApp.init({
|
@@ -498,4 +586,7 @@ is changed to
|
|
498
586
|
|
499
587
|
### ShopifyAPI changes
|
500
588
|
|
501
|
-
You will need to also follow the ShopifyAPI [upgrade guide](https://github.com/
|
589
|
+
You will need to also follow the ShopifyAPI [upgrade guide](https://github.com/Shopify/shopify_api/blob/master/README.md#-breaking-change-notice-for-version-700-) to ensure your app is ready to work with api versioning.
|
590
|
+
|
591
|
+
[dashboard]:https://partners.shopify.com
|
592
|
+
[app-bridge]:https://help.shopify.com/en/api/embedded-apps/app-bridge
|
@@ -4,31 +4,31 @@
|
|
4
4
|
this.itpAction = document.getElementById('TopLevelInteractionButton');
|
5
5
|
this.redirectUrl = opts.redirectUrl;
|
6
6
|
}
|
7
|
-
|
7
|
+
|
8
8
|
ITPHelper.prototype.redirect = function() {
|
9
9
|
sessionStorage.setItem('shopify.top_level_interaction', true);
|
10
10
|
window.location.href = this.redirectUrl;
|
11
11
|
}
|
12
|
-
|
12
|
+
|
13
13
|
ITPHelper.prototype.userAgentIsAffected = function() {
|
14
14
|
return Boolean(document.hasStorageAccess);
|
15
15
|
}
|
16
|
-
|
16
|
+
|
17
17
|
ITPHelper.prototype.canPartitionCookies = function() {
|
18
18
|
var versionRegEx = /Version\/12\.0\.?\d? Safari/;
|
19
19
|
return versionRegEx.test(navigator.userAgent);
|
20
20
|
}
|
21
|
-
|
21
|
+
|
22
22
|
ITPHelper.prototype.setUpContent = function(onClick) {
|
23
23
|
this.itpContent.style.display = 'block';
|
24
24
|
this.itpAction.addEventListener('click', this.redirect.bind(this));
|
25
25
|
}
|
26
|
-
|
26
|
+
|
27
27
|
ITPHelper.prototype.execute = function() {
|
28
28
|
if (!this.itpContent) {
|
29
29
|
return;
|
30
30
|
}
|
31
|
-
|
31
|
+
|
32
32
|
if (this.userAgentIsAffected()) {
|
33
33
|
this.setUpContent();
|
34
34
|
} else {
|
@@ -28,18 +28,47 @@
|
|
28
28
|
window.parent.location.href = this.redirectData.myshopifyUrl + '/admin/apps';
|
29
29
|
}
|
30
30
|
|
31
|
-
StorageAccessHelper.prototype.
|
32
|
-
window.location.href = this.redirectData.
|
31
|
+
StorageAccessHelper.prototype.redirectToAppTargetUrl = function() {
|
32
|
+
window.location.href = this.redirectData.appTargetUrl;
|
33
|
+
}
|
34
|
+
|
35
|
+
StorageAccessHelper.prototype.sameSiteNoneIncompatible = function(ua) {
|
36
|
+
return ua.includes("iPhone OS 12_") || ua.includes("iPad; CPU OS 12_") || //iOS 12
|
37
|
+
(ua.includes("UCBrowser/")
|
38
|
+
? this.isOlderUcBrowser(ua) //UC Browser < 12.13.2
|
39
|
+
: (ua.includes("Chrome/5") || ua.includes("Chrome/6"))) ||
|
40
|
+
ua.includes("Chromium/5") || ua.includes("Chromium/6") ||
|
41
|
+
(ua.includes(" OS X 10_14_") &&
|
42
|
+
((ua.includes("Version/") && ua.includes("Safari")) || //Safari on MacOS 10.14
|
43
|
+
ua.endsWith("(KHTML, like Gecko)"))); //Web view on MacOS 10.14
|
44
|
+
}
|
45
|
+
|
46
|
+
StorageAccessHelper.prototype.isOlderUcBrowser = function(ua) {
|
47
|
+
var match = ua.match(/UCBrowser\/(\d+)\.(\d+)\.(\d+)\./);
|
48
|
+
if (!match) return false;
|
49
|
+
var major = parseInt(match[1]);
|
50
|
+
var minor = parseInt(match[2]);
|
51
|
+
var build = parseInt(match[3]);
|
52
|
+
if (major != 12) return major < 12;
|
53
|
+
if (minor != 13) return minor < 13;
|
54
|
+
return build < 2;
|
55
|
+
}
|
56
|
+
|
57
|
+
StorageAccessHelper.prototype.setCookie = function(value) {
|
58
|
+
if(!this.sameSiteNoneIncompatible(navigator.userAgent)) {
|
59
|
+
value += '; secure; SameSite=None'
|
60
|
+
}
|
61
|
+
document.cookie = value;
|
33
62
|
}
|
34
63
|
|
35
64
|
StorageAccessHelper.prototype.grantedStorageAccess = function() {
|
36
65
|
try {
|
37
66
|
sessionStorage.setItem('shopify.granted_storage_access', true);
|
38
|
-
|
67
|
+
this.setCookie('shopify.granted_storage_access=true');
|
39
68
|
if (!document.cookie) {
|
40
69
|
throw 'Cannot set third-party cookie.'
|
41
70
|
}
|
42
|
-
this.
|
71
|
+
this.redirectToAppTargetUrl();
|
43
72
|
} catch (error) {
|
44
73
|
console.warn('Third party cookies may be blocked.', error);
|
45
74
|
this.redirectToAppTLD(ACCESS_DENIED_STATUS);
|
@@ -61,7 +90,7 @@
|
|
61
90
|
StorageAccessHelper.prototype.handleHasStorageAccess = function() {
|
62
91
|
if (sessionStorage.getItem('shopify.granted_storage_access')) {
|
63
92
|
// If app was classified by ITP and used Storage Access API to acquire access
|
64
|
-
this.
|
93
|
+
this.redirectToAppTargetUrl();
|
65
94
|
} else {
|
66
95
|
// If app has not been classified by ITP and still has storage access
|
67
96
|
this.redirectToAppTLD(ACCESS_GRANTED_STATUS);
|
@@ -103,11 +132,11 @@
|
|
103
132
|
|
104
133
|
/* ITP 2.0 solution: handles cookie partitioning */
|
105
134
|
StorageAccessHelper.prototype.setUpHelper = function() {
|
106
|
-
return new ITPHelper({redirectUrl: window.shopOrigin + "/admin/apps/" + window.apiKey});
|
135
|
+
return new ITPHelper({redirectUrl: window.shopOrigin + "/admin/apps/" + window.apiKey + window.returnTo});
|
107
136
|
}
|
108
137
|
|
109
138
|
StorageAccessHelper.prototype.setCookieAndRedirect = function() {
|
110
|
-
|
139
|
+
this.setCookie('shopify.cookies_persist=true');
|
111
140
|
var helper = this.setUpHelper();
|
112
141
|
helper.redirect();
|
113
142
|
}
|
@@ -8,8 +8,8 @@ module ShopifyApp
|
|
8
8
|
include ShopifyApp::Localization
|
9
9
|
include ShopifyApp::LoginProtection
|
10
10
|
include ShopifyApp::EmbeddedApp
|
11
|
-
before_action :
|
12
|
-
around_action :
|
11
|
+
before_action :login_again_if_different_user_or_shop
|
12
|
+
around_action :activate_shopify_session
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|