ruby_shopify_app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.babelrc +5 -0
- data/.github/CODEOWNERS +2 -0
- data/.github/ISSUE_TEMPLATE/bug-report.md +63 -0
- data/.github/ISSUE_TEMPLATE/config.yml +1 -0
- data/.github/ISSUE_TEMPLATE/feature-request.md +33 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.github/probots.yml +2 -0
- data/.github/workflows/build.yml +40 -0
- data/.github/workflows/release.yml +24 -0
- data/.github/workflows/rubocop.yml +22 -0
- data/.gitignore +14 -0
- data/.nvmrc +1 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG-OLD.md +643 -0
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +81 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +280 -0
- data/LICENSE +19 -0
- data/README.md +132 -0
- data/Rakefile +7 -0
- data/SECURITY.md +59 -0
- data/app/assets/images/storage_access.svg +1 -0
- data/app/assets/javascripts/shopify_app/app_bridge_2.0.12.js +10 -0
- data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +22 -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 +8 -0
- data/app/assets/javascripts/shopify_app/post_redirect.js +9 -0
- data/app/assets/javascripts/shopify_app/redirect.js +31 -0
- data/app/assets/javascripts/shopify_app/request_storage_access.js +3 -0
- data/app/assets/javascripts/shopify_app/storage_access.js +148 -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/concerns/shopify_app/authenticated.rb +16 -0
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +39 -0
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +40 -0
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +32 -0
- data/app/controllers/shopify_app/authenticated_controller.rb +8 -0
- data/app/controllers/shopify_app/callback_controller.rb +195 -0
- data/app/controllers/shopify_app/extension_verification_controller.rb +15 -0
- data/app/controllers/shopify_app/sessions_controller.rb +202 -0
- data/app/controllers/shopify_app/webhooks_controller.rb +36 -0
- data/app/views/shopify_app/partials/_button_styles.html.erb +109 -0
- data/app/views/shopify_app/partials/_card_styles.html.erb +33 -0
- data/app/views/shopify_app/partials/_empty_state_styles.html.erb +98 -0
- data/app/views/shopify_app/partials/_form_styles.html.erb +56 -0
- data/app/views/shopify_app/partials/_layout_styles.html.erb +182 -0
- data/app/views/shopify_app/partials/_typography_styles.html.erb +35 -0
- data/app/views/shopify_app/sessions/enable_cookies.html.erb +70 -0
- data/app/views/shopify_app/sessions/new.html.erb +51 -0
- data/app/views/shopify_app/sessions/request_storage_access.html.erb +68 -0
- data/app/views/shopify_app/sessions/top_level_interaction.html.erb +63 -0
- data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +13 -0
- data/app/views/shopify_app/shared/redirect.html.erb +23 -0
- data/config/locales/cs.yml +23 -0
- data/config/locales/da.yml +20 -0
- data/config/locales/de.yml +22 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/es.yml +22 -0
- data/config/locales/fi.yml +20 -0
- data/config/locales/fr.yml +23 -0
- data/config/locales/it.yml +21 -0
- data/config/locales/ja.yml +17 -0
- data/config/locales/ko.yml +19 -0
- data/config/locales/nb.yml +21 -0
- data/config/locales/nl.yml +21 -0
- data/config/locales/pl.yml +21 -0
- data/config/locales/pt-BR.yml +21 -0
- 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/vi.yml +22 -0
- data/config/locales/zh-CN.yml +16 -0
- data/config/locales/zh-TW.yml +16 -0
- data/config/routes.rb +23 -0
- data/docs/Quickstart.md +31 -0
- data/docs/Releasing.md +21 -0
- data/docs/Troubleshooting.md +159 -0
- data/docs/Upgrading.md +132 -0
- data/docs/shopify_app/authentication.md +124 -0
- data/docs/shopify_app/engine.md +82 -0
- data/docs/shopify_app/generators.md +127 -0
- data/docs/shopify_app/handling-access-scopes-changes.md +24 -0
- data/docs/shopify_app/script-tags.md +28 -0
- data/docs/shopify_app/session-repository.md +88 -0
- data/docs/shopify_app/testing.md +38 -0
- data/docs/shopify_app/webhooks.md +72 -0
- data/images/app-proxy-screenshot.png +0 -0
- data/karma.conf.js +44 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +47 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +11 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +40 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +62 -0
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +69 -0
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +13 -0
- data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +26 -0
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +8 -0
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +11 -0
- data/lib/generators/shopify_app/app_proxy_controller/templates/index.html.erb +19 -0
- data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +15 -0
- data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +5 -0
- data/lib/generators/shopify_app/controllers/controllers_generator.rb +30 -0
- data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +53 -0
- data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +18 -0
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +75 -0
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +12 -0
- data/lib/generators/shopify_app/install/install_generator.rb +121 -0
- data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +3 -0
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +44 -0
- data/lib/generators/shopify_app/install/templates/flash_messages.js +24 -0
- data/lib/generators/shopify_app/install/templates/omniauth.rb +4 -0
- data/lib/generators/shopify_app/install/templates/session_store.rb +4 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.js +15 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +25 -0
- data/lib/generators/shopify_app/install/templates/shopify_app_importmap.js +13 -0
- data/lib/generators/shopify_app/install/templates/shopify_app_index.js +2 -0
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +8 -0
- data/lib/generators/shopify_app/install/templates/user_agent.rb +6 -0
- data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +19 -0
- data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +8 -0
- 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/routes/routes_generator.rb +32 -0
- data/lib/generators/shopify_app/routes/templates/routes.rb +12 -0
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +70 -0
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +5 -0
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/create_shops.erb +15 -0
- data/lib/generators/shopify_app/shop_model/templates/shop.rb +8 -0
- data/lib/generators/shopify_app/shop_model/templates/shops.yml +3 -0
- data/lib/generators/shopify_app/shopify_app_generator.rb +18 -0
- data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +5 -0
- 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 +8 -0
- data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +70 -0
- data/lib/generators/shopify_app/views/views_generator.rb +30 -0
- data/lib/shopify_app/access_scopes/noop_strategy.rb +13 -0
- data/lib/shopify_app/access_scopes/shop_strategy.rb +24 -0
- data/lib/shopify_app/access_scopes/user_strategy.rb +41 -0
- data/lib/shopify_app/configuration.rb +119 -0
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +38 -0
- data/lib/shopify_app/controller_concerns/csrf_protection.rb +15 -0
- data/lib/shopify_app/controller_concerns/embedded_app.rb +20 -0
- data/lib/shopify_app/controller_concerns/itp.rb +45 -0
- data/lib/shopify_app/controller_concerns/localization.rb +23 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +259 -0
- data/lib/shopify_app/controller_concerns/payload_verification.rb +24 -0
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +23 -0
- data/lib/shopify_app/engine.rb +47 -0
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +16 -0
- data/lib/shopify_app/jobs/webhooks_manager_job.rb +16 -0
- data/lib/shopify_app/managers/scripttags_manager.rb +78 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +62 -0
- data/lib/shopify_app/middleware/jwt_middleware.rb +43 -0
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +34 -0
- data/lib/shopify_app/omniauth/omniauth_configuration.rb +64 -0
- data/lib/shopify_app/session/in_memory_session_store.rb +31 -0
- data/lib/shopify_app/session/in_memory_shop_session_store.rb +16 -0
- data/lib/shopify_app/session/in_memory_user_session_store.rb +16 -0
- data/lib/shopify_app/session/jwt.rb +67 -0
- data/lib/shopify_app/session/null_user_session_store.rb +22 -0
- data/lib/shopify_app/session/session_repository.rb +56 -0
- data/lib/shopify_app/session/session_storage.rb +20 -0
- data/lib/shopify_app/session/shop_session_storage.rb +42 -0
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +58 -0
- data/lib/shopify_app/session/user_session_storage.rb +42 -0
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +58 -0
- data/lib/shopify_app/test_helpers/all.rb +2 -0
- data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
- data/lib/shopify_app/utils.rb +37 -0
- data/lib/shopify_app/version.rb +4 -0
- data/lib/shopify_app.rb +80 -0
- data/package.json +27 -0
- data/service.yml +4 -0
- data/shipit.rubygems.yml +4 -0
- data/shopify_app.gemspec +39 -0
- data/translation.yml +7 -0
- data/webpack.config.js +24 -0
- data/yarn.lock +5230 -0
- metadata +465 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class HomeController < AuthenticatedController
|
|
4
|
+
include ShopifyApp::ShopAccessScopesVerification
|
|
5
|
+
|
|
6
|
+
before_action :set_host
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
|
|
10
|
+
@webhooks = ShopifyAPI::Webhook.find(:all)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def set_host
|
|
16
|
+
@host = params[:host]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= I18n.locale %>">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<link
|
|
7
|
+
rel="stylesheet"
|
|
8
|
+
href="https://unpkg.com/@shopify/polaris@4.25.0/styles.min.css"
|
|
9
|
+
/>
|
|
10
|
+
<% unless with_cookie_authentication? %> <script>
|
|
11
|
+
document.addEventListener("DOMContentLoaded", async function() {
|
|
12
|
+
<% if ShopifyApp.use_importmap? %>
|
|
13
|
+
await import("lib/shopify_app")
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
var SessionToken = window["app-bridge"].actions.SessionToken
|
|
17
|
+
var app = window.app;
|
|
18
|
+
|
|
19
|
+
app.dispatch(
|
|
20
|
+
SessionToken.request(),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Save a session token for future requests
|
|
24
|
+
window.sessionToken = await new Promise((resolve) => {
|
|
25
|
+
app.subscribe(SessionToken.Action.RESPOND, (data) => {
|
|
26
|
+
resolve(data.sessionToken || "");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
var fetchProducts = function() {
|
|
31
|
+
var headers = new Headers({ "Authorization": "Bearer " + window.sessionToken });
|
|
32
|
+
return fetch("/products", { headers })
|
|
33
|
+
.then(response => response.json())
|
|
34
|
+
.then(data => {
|
|
35
|
+
var products = data.products;
|
|
36
|
+
|
|
37
|
+
if (products === undefined || products.length == 0) {
|
|
38
|
+
document.getElementById("products").innerHTML = "<br>No products to display.";
|
|
39
|
+
} else {
|
|
40
|
+
var list = "";
|
|
41
|
+
products.forEach((product) => {
|
|
42
|
+
var link = `<a target="_top" href="https://<%%= @shop_origin %>/admin/products/${product.id}">`
|
|
43
|
+
list += "<li>" + link + product.title + "</a></li>";
|
|
44
|
+
});
|
|
45
|
+
document.getElementById("products").innerHTML = "<ul>" + list + "</ul>";
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}();
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
<% end %> </head>
|
|
52
|
+
<body>
|
|
53
|
+
<h2>Products</h2>
|
|
54
|
+
<% unless with_cookie_authentication? %> <div id="products"><br>Loading...</div><% else %>
|
|
55
|
+
<ul>
|
|
56
|
+
<%% @products.each do |product| %>
|
|
57
|
+
<li><%%= link_to product.title, "https://#{@current_shopify_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
|
|
58
|
+
<%% end %>
|
|
59
|
+
</ul>
|
|
60
|
+
|
|
61
|
+
<hr>
|
|
62
|
+
<% end %>
|
|
63
|
+
<h2>Webhooks</h2>
|
|
64
|
+
|
|
65
|
+
<%% if @webhooks.present? %>
|
|
66
|
+
<ul>
|
|
67
|
+
<%% @webhooks.each do |webhook| %>
|
|
68
|
+
<li><%%= webhook.topic %> : <%%= webhook.address %></li>
|
|
69
|
+
<%% end %>
|
|
70
|
+
</ul>
|
|
71
|
+
<%% else %>
|
|
72
|
+
<p>This app has not created any webhooks for this Shop. Add webhooks to your ShopifyApp initializer if you need webhooks</p>
|
|
73
|
+
<%% end %>
|
|
74
|
+
</body>
|
|
75
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class HomeController < ApplicationController
|
|
4
|
+
include ShopifyApp::EmbeddedApp
|
|
5
|
+
include ShopifyApp::RequireKnownShop
|
|
6
|
+
include ShopifyApp::ShopAccessScopesVerification
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@shop_origin = current_shopify_domain
|
|
10
|
+
@host = params[:host]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rails/generators/base'
|
|
3
|
+
|
|
4
|
+
module ShopifyApp
|
|
5
|
+
module Generators
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
include Rails::Generators::Migration
|
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
9
|
+
|
|
10
|
+
class_option :application_name, type: :array, default: ['My', 'Shopify', 'App']
|
|
11
|
+
class_option :scope, type: :array, default: ['read_products']
|
|
12
|
+
class_option :embedded, type: :string, default: 'true'
|
|
13
|
+
class_option :api_version, type: :string, default: nil
|
|
14
|
+
class_option :with_cookie_authentication, type: :boolean, default: false
|
|
15
|
+
|
|
16
|
+
def create_shopify_app_initializer
|
|
17
|
+
@application_name = format_array_argument(options['application_name'])
|
|
18
|
+
@scope = format_array_argument(options['scope'])
|
|
19
|
+
@api_version = options['api_version'] || ShopifyAPI::Meta.admin_versions.find(&:latest_supported).handle
|
|
20
|
+
|
|
21
|
+
template('shopify_app.rb', 'config/initializers/shopify_app.rb')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_session_store_initializer
|
|
25
|
+
copy_file('session_store.rb', 'config/initializers/session_store.rb')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_and_inject_into_omniauth_initializer
|
|
29
|
+
unless File.exist?("config/initializers/omniauth.rb")
|
|
30
|
+
copy_file('omniauth.rb', 'config/initializers/omniauth.rb')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return if !Rails.env.test? && shopify_provider_exists?
|
|
34
|
+
|
|
35
|
+
inject_into_file(
|
|
36
|
+
'config/initializers/omniauth.rb',
|
|
37
|
+
shopify_provider_template,
|
|
38
|
+
after: "Rails.application.config.middleware.use(OmniAuth::Builder) do\n"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def create_embedded_app_layout
|
|
43
|
+
return unless embedded_app?
|
|
44
|
+
|
|
45
|
+
copy_file('embedded_app.html.erb', 'app/views/layouts/embedded_app.html.erb')
|
|
46
|
+
copy_file('_flash_messages.html.erb', 'app/views/layouts/_flash_messages.html.erb')
|
|
47
|
+
|
|
48
|
+
if ShopifyApp.use_webpacker?
|
|
49
|
+
copy_file('shopify_app.js', 'app/javascript/shopify_app/shopify_app.js')
|
|
50
|
+
copy_file('flash_messages.js', 'app/javascript/shopify_app/flash_messages.js')
|
|
51
|
+
copy_file('shopify_app_index.js', 'app/javascript/shopify_app/index.js')
|
|
52
|
+
append_to_file('app/javascript/packs/application.js', "require(\"shopify_app\")\n")
|
|
53
|
+
elsif ShopifyApp.use_importmap?
|
|
54
|
+
copy_file('shopify_app_importmap.js', 'app/javascript/lib/shopify_app.js')
|
|
55
|
+
copy_file('flash_messages.js', 'app/javascript/lib/flash_messages.js')
|
|
56
|
+
append_to_file('config/importmap.rb', "pin_all_from \"app/javascript/lib\", under: \"lib\"\n")
|
|
57
|
+
else
|
|
58
|
+
copy_file('shopify_app.js', 'app/assets/javascripts/shopify_app.js')
|
|
59
|
+
copy_file('flash_messages.js', 'app/assets/javascripts/flash_messages.js')
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def create_user_agent_initializer
|
|
64
|
+
template('user_agent.rb', 'config/initializers/user_agent.rb')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def mount_engine
|
|
68
|
+
route("mount ShopifyApp::Engine, at: '/'")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def insert_hosts_into_development_config
|
|
72
|
+
inject_into_file(
|
|
73
|
+
'config/environments/development.rb',
|
|
74
|
+
" config.hosts = (config.hosts rescue []) << /\[-\\w]+\\.ngrok\\.io/\n",
|
|
75
|
+
after: "Rails.application.configure do\n"
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def shopify_provider_exists?
|
|
82
|
+
File.open("config/initializers/omniauth.rb") do |file|
|
|
83
|
+
file.each_line do |line|
|
|
84
|
+
if line =~ /provider :shopify/
|
|
85
|
+
puts "\e[33m#{omniauth_warning}\e[0m"
|
|
86
|
+
return true
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def omniauth_warning
|
|
94
|
+
<<~OMNIAUTH
|
|
95
|
+
\n[WARNING] The Shopify App generator attempted to add the following Shopify Omniauth \
|
|
96
|
+
provider 'config/initializers/omniauth.rb':
|
|
97
|
+
|
|
98
|
+
\e[0m#{shopify_provider_template}\e[33m
|
|
99
|
+
|
|
100
|
+
Consider updating 'config/initializers/omniauth.rb' to match the configuration above.
|
|
101
|
+
OMNIAUTH
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def shopify_provider_template
|
|
105
|
+
File.read(File.expand_path(find_in_source_paths('shopify_provider.rb.tt')))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def embedded_app?
|
|
109
|
+
options['embedded'] == 'true'
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def format_array_argument(array)
|
|
113
|
+
array.join(' ').tr('"', '')
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def with_cookie_authentication?
|
|
117
|
+
options['with_cookie_authentication'] || !embedded_app?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<% application_name = ShopifyApp.configuration.application_name %>
|
|
6
|
+
<title><%= application_name %></title>
|
|
7
|
+
<%= stylesheet_link_tag 'application' %>
|
|
8
|
+
<% if ShopifyApp.use_webpacker? %>
|
|
9
|
+
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
|
|
10
|
+
<% elsif ShopifyApp.use_importmap? %>
|
|
11
|
+
<%= javascript_importmap_tags %>
|
|
12
|
+
<% else %>
|
|
13
|
+
<%= javascript_include_tag 'application', "data-turbolinks-track" => true %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= csrf_meta_tags %>
|
|
16
|
+
</head>
|
|
17
|
+
|
|
18
|
+
<body>
|
|
19
|
+
<div class="app-wrapper">
|
|
20
|
+
<div class="app-content">
|
|
21
|
+
<main role="main">
|
|
22
|
+
<%= yield %>
|
|
23
|
+
</main>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<%= render 'layouts/flash_messages' %>
|
|
28
|
+
|
|
29
|
+
<script src="https://unpkg.com/@shopify/app-bridge@2"></script>
|
|
30
|
+
|
|
31
|
+
<%= content_tag(:div, nil, id: 'shopify-app-init', data: {
|
|
32
|
+
api_key: ShopifyApp.configuration.api_key,
|
|
33
|
+
shop_origin: @shop_origin || (@current_shopify_session.domain if @current_shopify_session),
|
|
34
|
+
host: @host,
|
|
35
|
+
debug: Rails.env.development?
|
|
36
|
+
} ) %>
|
|
37
|
+
|
|
38
|
+
<% if content_for?(:javascript) %>
|
|
39
|
+
<div id="ContentForJavascript" data-turbolinks-temporary>
|
|
40
|
+
<%= yield :javascript %>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var eventName = typeof(Turbolinks) !== 'undefined' ? 'turbolinks:load' : 'DOMContentLoaded';
|
|
2
|
+
|
|
3
|
+
if (!document.documentElement.hasAttribute("data-turbolinks-preview")) {
|
|
4
|
+
document.addEventListener(eventName, function flash() {
|
|
5
|
+
var flashData = JSON.parse(document.getElementById('shopify-app-flash').dataset.flash);
|
|
6
|
+
|
|
7
|
+
var Toast = window['app-bridge'].actions.Toast;
|
|
8
|
+
|
|
9
|
+
if (flashData.notice) {
|
|
10
|
+
Toast.create(app, {
|
|
11
|
+
message: flashData.notice,
|
|
12
|
+
duration: 5000,
|
|
13
|
+
}).dispatch(Toast.Action.SHOW);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (flashData.error) {
|
|
17
|
+
Toast.create(app, {
|
|
18
|
+
message: flashData.error,
|
|
19
|
+
duration: 5000,
|
|
20
|
+
isError: true,
|
|
21
|
+
}).dispatch(Toast.Action.SHOW);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
+
var data = document.getElementById('shopify-app-init').dataset;
|
|
3
|
+
var AppBridge = window['app-bridge'];
|
|
4
|
+
var createApp = AppBridge.default;
|
|
5
|
+
window.app = createApp({
|
|
6
|
+
apiKey: data.apiKey,
|
|
7
|
+
host: data.host,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
var actions = AppBridge.actions;
|
|
11
|
+
var TitleBar = actions.TitleBar;
|
|
12
|
+
TitleBar.create(app, {
|
|
13
|
+
title: data.page,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
ShopifyApp.configure do |config|
|
|
2
|
+
config.application_name = "<%= @application_name %>"
|
|
3
|
+
config.old_secret = "<%= @old_secret %>"
|
|
4
|
+
config.scope = "<%= @scope %>" # Consult this page for more scope options:
|
|
5
|
+
# https://help.shopify.com/en/api/getting-started/authentication/oauth/scopes
|
|
6
|
+
config.embedded_app = <%= embedded_app? %>
|
|
7
|
+
config.after_authenticate_job = false
|
|
8
|
+
config.api_version = "<%= @api_version %>"
|
|
9
|
+
config.shop_session_repository = 'Shop'
|
|
10
|
+
|
|
11
|
+
config.reauth_on_access_scope_changes = true
|
|
12
|
+
|
|
13
|
+
config.allow_jwt_authentication = <%= !with_cookie_authentication? %>
|
|
14
|
+
config.allow_cookie_authentication = <%= with_cookie_authentication? %>
|
|
15
|
+
|
|
16
|
+
config.api_key = ENV.fetch('SHOPIFY_API_KEY', '').presence
|
|
17
|
+
config.secret = ENV.fetch('SHOPIFY_API_SECRET', '').presence
|
|
18
|
+
if defined? Rails::Server
|
|
19
|
+
raise('Missing SHOPIFY_API_KEY. See https://github.com/Shopify/shopify_app#requirements') unless config.api_key
|
|
20
|
+
raise('Missing SHOPIFY_API_SECRET. See https://github.com/Shopify/shopify_app#requirements') unless config.secret
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# ShopifyApp::Utils.fetch_known_api_versions # Uncomment to fetch known api versions from shopify servers on boot
|
|
25
|
+
# ShopifyAPI::ApiVersion.version_lookup_mode = :raise_on_unknown # Uncomment to raise an error if attempting to use an api version that was not previously known
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
var data = document.getElementById('shopify-app-init').dataset;
|
|
2
|
+
var AppBridge = window['app-bridge'];
|
|
3
|
+
var createApp = AppBridge.default;
|
|
4
|
+
window.app = createApp({
|
|
5
|
+
apiKey: data.apiKey,
|
|
6
|
+
host: data.host,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
var actions = AppBridge.actions;
|
|
10
|
+
var TitleBar = actions.TitleBar;
|
|
11
|
+
TitleBar.create(app, {
|
|
12
|
+
title: data.page,
|
|
13
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
provider :shopify,
|
|
2
|
+
ShopifyApp.configuration.api_key,
|
|
3
|
+
ShopifyApp.configuration.secret,
|
|
4
|
+
scope: ShopifyApp.configuration.scope,
|
|
5
|
+
setup: lambda { |env|
|
|
6
|
+
configuration = ShopifyApp::OmniAuthConfiguration.new(env['omniauth.strategy'], Rack::Request.new(env))
|
|
7
|
+
configuration.build_options
|
|
8
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
|
|
5
|
+
module ShopifyApp
|
|
6
|
+
module Generators
|
|
7
|
+
class ProductsControllerGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
9
|
+
|
|
10
|
+
def create_products_controller
|
|
11
|
+
template('products_controller.rb', 'app/controllers/products_controller.rb')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add_products_route
|
|
15
|
+
route("get '/products', :to => 'products#index'")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
|
|
5
|
+
module ShopifyApp
|
|
6
|
+
module Generators
|
|
7
|
+
class RotateShopifyTokenJobGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
9
|
+
|
|
10
|
+
def add_rotate_shopify_token_job
|
|
11
|
+
copy_file('rotate_shopify_token_job.rb', "app/jobs/shopify/rotate_shopify_token_job.rb")
|
|
12
|
+
copy_file('rotate_shopify_token.rake', "lib/tasks/shopify/rotate_shopify_token.rake")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
namespace :shopify do
|
|
3
|
+
desc "Rotate shopify tokens for all active shops"
|
|
4
|
+
task :rotate_shopify_tokens, [:refresh_token] => :environment do |_t, args|
|
|
5
|
+
all_active_shops.find_each do |shop|
|
|
6
|
+
Shopify::RotateShopifyTokenJob.perform_later(
|
|
7
|
+
shop_domain: shop.shopify_domain,
|
|
8
|
+
refresh_token: args[:refresh_token]
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Its implementation will depend on the app configuration. Change accordingly.
|
|
14
|
+
def all_active_shops
|
|
15
|
+
Shop.all
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shopify
|
|
4
|
+
class RotateShopifyTokenJob < ActiveJob::Base
|
|
5
|
+
def perform(params)
|
|
6
|
+
@shop = Shop.find_by(shopify_domain: params[:shop_domain])
|
|
7
|
+
return unless @shop
|
|
8
|
+
|
|
9
|
+
config = ShopifyApp.configuration
|
|
10
|
+
uri = URI("https://#{@shop.shopify_domain}/admin/oauth/access_token")
|
|
11
|
+
post_data = {
|
|
12
|
+
client_id: config.api_key,
|
|
13
|
+
client_secret: config.secret,
|
|
14
|
+
refresh_token: params[:refresh_token],
|
|
15
|
+
access_token: @shop.shopify_token,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@response = Net::HTTP.post_form(uri, post_data)
|
|
19
|
+
return log_error(response_exception_error_message) unless @response.is_a?(Net::HTTPSuccess)
|
|
20
|
+
|
|
21
|
+
access_token = JSON.parse(@response.body)['access_token']
|
|
22
|
+
return log_error(no_access_token_error_message) unless access_token
|
|
23
|
+
|
|
24
|
+
@shop.update(shopify_token: access_token)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def log_error(message)
|
|
30
|
+
Rails.logger.error(message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def no_access_token_error_message
|
|
34
|
+
"RotateShopifyTokenJob response returned no access token for shop: #{@shop.shopify_domain}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def response_exception_error_message
|
|
38
|
+
"RotateShopifyTokenJob failed for shop: #{@shop.shopify_domain}." \
|
|
39
|
+
"Response returned status: #{@response.code}. Error message: #{@response.message}. "
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rails/generators/base'
|
|
3
|
+
|
|
4
|
+
module ShopifyApp
|
|
5
|
+
module Generators
|
|
6
|
+
class RoutesGenerator < Rails::Generators::Base
|
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
8
|
+
|
|
9
|
+
def inject_shopify_app_routes_into_application_routes
|
|
10
|
+
route(session_routes)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def disable_engine_routes
|
|
14
|
+
gsub_file(
|
|
15
|
+
'config/routes.rb',
|
|
16
|
+
"mount ShopifyApp::Engine, at: '/'",
|
|
17
|
+
''
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def session_routes
|
|
24
|
+
File.read(routes_file_path)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def routes_file_path
|
|
28
|
+
File.expand_path(find_in_source_paths('routes.rb'))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
controller :sessions do
|
|
4
|
+
get 'login' => :new, :as => :login
|
|
5
|
+
post 'login' => :create, :as => :authenticate
|
|
6
|
+
get 'auth/shopify/callback' => :callback
|
|
7
|
+
get 'logout' => :destroy, :as => :logout
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
namespace :webhooks do
|
|
11
|
+
post ':type' => :receive
|
|
12
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rails/generators/base'
|
|
3
|
+
require 'rails/generators/active_record'
|
|
4
|
+
|
|
5
|
+
module ShopifyApp
|
|
6
|
+
module Generators
|
|
7
|
+
class ShopModelGenerator < Rails::Generators::Base
|
|
8
|
+
include Rails::Generators::Migration
|
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
10
|
+
|
|
11
|
+
class_option :new_shopify_cli_app, type: :boolean, default: false
|
|
12
|
+
|
|
13
|
+
def create_shop_model
|
|
14
|
+
copy_file('shop.rb', 'app/models/shop.rb')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_shop_migration
|
|
18
|
+
migration_template('db/migrate/create_shops.erb', 'db/migrate/create_shops.rb')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_shop_with_access_scopes_migration
|
|
22
|
+
scopes_column_prompt = <<~PROMPT
|
|
23
|
+
It is highly recommended that apps record the access scopes granted by \
|
|
24
|
+
merchants during app installation. See app/models/shop.rb to modify how \
|
|
25
|
+
access scopes are stored and retrieved.
|
|
26
|
+
|
|
27
|
+
[WARNING] You will need to update the access_scopes accessors in the Shop model \
|
|
28
|
+
to allow shopify_app to store and retrieve scopes when going through OAuth.
|
|
29
|
+
|
|
30
|
+
The following migration will add an `access_scopes` column to the Shop model. \
|
|
31
|
+
Do you want to include this migration? [y/n]
|
|
32
|
+
PROMPT
|
|
33
|
+
|
|
34
|
+
if new_shopify_cli_app? || Rails.env.test? || yes?(scopes_column_prompt)
|
|
35
|
+
migration_template(
|
|
36
|
+
'db/migrate/add_shop_access_scopes_column.erb',
|
|
37
|
+
'db/migrate/add_shop_access_scopes_column.rb'
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update_shopify_app_initializer
|
|
43
|
+
gsub_file('config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryShopSessionStore', 'Shop')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_shop_fixtures
|
|
47
|
+
copy_file('shops.yml', 'test/fixtures/shops.yml')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def new_shopify_cli_app?
|
|
53
|
+
options['new_shopify_cli_app']
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def rails_migration_version
|
|
57
|
+
Rails.version.match(/\d\.\d/)[0]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class << self
|
|
61
|
+
private :next_migration_number
|
|
62
|
+
|
|
63
|
+
# for generating a timestamp when using `create_migration`
|
|
64
|
+
def next_migration_number(dir)
|
|
65
|
+
ActiveRecord::Generators::Base.next_migration_number(dir)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateShops < ActiveRecord::Migration[<%= rails_migration_version %>]
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :shops do |t|
|
|
4
|
+
t.string :shopify_domain, null: false
|
|
5
|
+
t.string :shopify_token, null: false
|
|
6
|
+
t.timestamps
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
add_index :shops, :shopify_domain, unique: true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.down
|
|
13
|
+
drop_table :shops
|
|
14
|
+
end
|
|
15
|
+
end
|