Skip to content

Commit

Permalink
Replace legacy app bridge with app bridge cdn (#361)
Browse files Browse the repository at this point in the history
* remove old config parameters

* use shopify global object from app bridge cdn

* remove unsupported turbolinks

* update tests

* Update to use the utils helper.

Force https on the url if there is one from the origin. This can help with some ngrok issues when testing locally and stops mix matched requests.

* Add some defensive checks to the token blade file

* need to write the url updates

* use the helper

* Update the test to still check for a correct api key

---------

Co-authored-by: Luke Walsh <[email protected]>
  • Loading branch information
enmaboya and Kyon147 authored Nov 14, 2024
1 parent c9eb7df commit 3e99d44
Show file tree
Hide file tree
Showing 13 changed files with 69 additions and 168 deletions.
17 changes: 3 additions & 14 deletions src/Http/Middleware/VerifyShopify.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,20 +377,9 @@ protected function getHmacFromRequest(Request $request): array
*/
protected function getAccessTokenFromRequest(Request $request): ?string
{
if (Util::getShopifyConfig('turbo_enabled')) {
if ($request->bearerToken()) {
// Bearer tokens collect.
// Turbo does not refresh the page, values are attached to the same header.
$bearerTokens = Collection::make(explode(',', $request->header('Authorization', '')));
$newestToken = Str::substr(trim($bearerTokens->last()), 7);

return $newestToken;
}

return $request->get('token');
}

return $this->isApiRequest($request) ? $request->bearerToken() : $request->get('token');
return $this->isApiRequest($request)
? $request->bearerToken()
: $request->get('token');
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/Traits/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public function authenticate(Request $request, AuthenticateShop $authShop)
'shopify-app::auth.fullpage_redirect',
[
'apiKey' => Util::getShopifyConfig('api_key', $shopOrigin),
'appBridgeVersion' => Util::getShopifyConfig('appbridge_version') ? '@'.config('shopify-app.appbridge_version') : '',
'authUrl' => $result['url'],
'url' => $result['url'],
'host' => $request->get('host'),
'shopDomain' => $shopDomain,
'locale' => $request->get('locale'),
Expand Down
33 changes: 0 additions & 33 deletions src/resources/config/shopify-app.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
*/

'domain' => env('SHOPIFY_DOMAIN'),

/*
|--------------------------------------------------------------------------
| Manual routes
Expand Down Expand Up @@ -138,26 +137,6 @@

'prefix' => env('SHOPIFY_APP_PREFIX', ''),

/*
|--------------------------------------------------------------------------
| AppBridge Mode
|--------------------------------------------------------------------------
|
| AppBridge (embedded apps) are enabled by default. Set to false to use legacy
| mode and host the app inside your own container.
|
*/

'appbridge_enabled' => (bool) env('SHOPIFY_APPBRIDGE_ENABLED', true),

// Use semver range to link to a major or minor version number.
// Leaving empty will use the latest version - not recommended in production.
'appbridge_version' => env('SHOPIFY_APPBRIDGE_VERSION', 'latest'),

// Set a new CDN URL if you want to host the AppBridge JS yourself or unpkg goes down.
// DO NOT include a trailing slash.
'appbridge_cdn_url' => env('SHOPIFY_APPBRIDGE_CDN_URL', 'https://unpkg.com'),

/*
|--------------------------------------------------------------------------
| Shopify App Name
Expand Down Expand Up @@ -513,18 +492,6 @@

'config_api_callback' => null,

/*
|--------------------------------------------------------------------------
| Enable Turbolinks or Hotwire Turbo
|--------------------------------------------------------------------------
|
| If you use Turbolinks/Turbo and Livewire, turn on this setting to get
| the token assigned automatically.
|
*/

'turbo_enabled' => (bool) env('SHOPIFY_TURBO_ENABLED', false),

/*
|--------------------------------------------------------------------------
| Customize Models and Table Name
Expand Down
23 changes: 5 additions & 18 deletions src/resources/views/auth/fullpage_redirect.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,19 @@
<head>
<meta charset="utf-8">
<base target="_top">
<meta name="shopify-api-key" content="{{ \Osiset\ShopifyApp\Util::getShopifyConfig('api_key', $shopDomain ?? Auth::user()->name ) }}"/>
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

<title>Redirecting...</title>

<script src="{{config('shopify-app.appbridge_cdn_url') ?? 'https://unpkg.com'}}/@shopify/app-bridge{!! $appBridgeVersion !!}"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
var redirectUrl = "{!! $authUrl !!}";
var normalizedLink;
let redirectUrl = "{!! $url !!}";
if (window.top === window.self) {
// If the current window is the 'parent', change the URL by setting location.href
window.top.location.href = redirectUrl;
} else {
// If the current window is the 'child', change the parent's URL with postMessage
normalizedLink = document.createElement('a');
normalizedLink.href = redirectUrl;
var AppBridge = window['app-bridge'];
var createApp = AppBridge.default;
var Redirect = AppBridge.actions.Redirect;
var app = createApp({
apiKey: "{{ $apiKey }}",
host: "{{ $host }}",
});
var redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.REMOTE, normalizedLink.href);
open(redirectUrl, '_top');
}
});
</script>
Expand Down
32 changes: 25 additions & 7 deletions src/resources/views/auth/token.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,31 @@

@section('scripts')
@parent

@if(config('shopify-app.appbridge_enabled'))
<script>
const host = new URLSearchParams(location.search).get("host")
utils.getSessionToken(app).then((token) => {
window.location.href = `{!! $target !!}{!! Str::contains($target, '?') ? '&' : '?' !!}token=${token}{{ Str::contains($target, 'host')? '' : '&host=${host}'}}`;
});
// If no host is found, we need to throw an error
const host = new URLSearchParams(location.search).get("host");
if (!host) {
throw new Error('No host found in the URL');
}
// If shopify is not defined, then we are not in a Shopify context redirect to the homepage as it
if (typeof shopify === 'undefined') {
open("{{ route('home') }}", "_self");
}
shopify.idToken().then((token) => {
let url = new URL(`{!! $target !!}`, window.location.origin);
// Enforce HTTPS if the current page is using HTTPS
if (window.location.protocol === 'https:') {
url.protocol = 'https:';
}
url.searchParams.set('token', token);
url.searchParams.set('host', host);
open(url.toString(), "_self");
history.pushState(null, '', url.toString());
});
</script>
@endif
@endsection
23 changes: 10 additions & 13 deletions src/resources/views/billing/fullpage_redirect.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,21 @@
<head>
<meta charset="utf-8">
<base target="_top">
<meta name="shopify-api-key" content="{{ config('shopify-app.api_key') }}" />
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

<title>Redirecting...</title>
<script src="{{config('shopify-app.appbridge_cdn_url') ?? 'https://unpkg.com'}}/@shopify/app-bridge{{ \Osiset\ShopifyApp\Util::getShopifyConfig('appbridge_version') ? '@'.config('shopify-app.appbridge_version') : '' }}"></script>

<script type="text/javascript">
const redirectUrl = "{!! $url !!}";
document.addEventListener('DOMContentLoaded', function () {
let redirectUrl = "{!! $url !!}";
const AppBridge = window['app-bridge'];
const createApp = AppBridge.default;
const Redirect = AppBridge.actions.Redirect;
const app = createApp({
apiKey: "{{ $apiKey }}",
host: "{{ $host }}",
if (window.top === window.self) {
window.top.location.href = redirectUrl;
} else {
open(redirectUrl, '_top');
}
});
console.log( 'app', app );
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.REMOTE, redirectUrl);
</script>
</head>
<body>
Expand Down
16 changes: 4 additions & 12 deletions src/resources/views/home/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
@endsection

@section('content')
<ui-title-bar title="Welcome"></ui-title-bar>

<div class="flex-center position-ref full-height">
<div class="content">
<div class="title m-b-md">
Expand All @@ -17,20 +19,10 @@
<p>&nbsp;</p>

<div class="links">
<a href="https://github.com/osiset/laravel-shopify" target="_blank">Package</a>
<a href="https://github.com/Kyon147/laravel-shopify" target="_blank">Package</a>
<a href="https://laravel.com" target="_blank">Laravel</a>
<a href="https://github.com/osiset/laravel-shopify" target="_blank">GitHub</a>
<a href="https://github.com/Kyon147/laravel-shopify" target="_blank">GitHub</a>
</div>
</div>
</div>
@endsection

@section('scripts')
@parent

@if(config('shopify-app.appbridge_enabled'))
<script>
actions.TitleBar.create(app, { title: 'Welcome' });
</script>
@endif
@endsection
25 changes: 4 additions & 21 deletions src/resources/views/layouts/default.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
<head>
<meta charset="utf-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="shopify-api-key" content="{{ \Osiset\ShopifyApp\Util::getShopifyConfig('api_key', $shopDomain ?? Auth::user()->name ) }}"/>
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

<title>{{ \Osiset\ShopifyApp\Util::getShopifyConfig('app_name') }}</title>
<title>{{ config('shopify-app.app_name') }}</title>
@yield('styles')
</head>

Expand All @@ -17,28 +19,9 @@
</div>
</div>

@if(\Osiset\ShopifyApp\Util::getShopifyConfig('appbridge_enabled') && \Osiset\ShopifyApp\Util::useNativeAppBridge())
<script src="{{config('shopify-app.appbridge_cdn_url') ?? 'https://unpkg.com'}}/@shopify/app-bridge{{ \Osiset\ShopifyApp\Util::getShopifyConfig('appbridge_version') ? '@'.config('shopify-app.appbridge_version') : '' }}"></script>
<script
@if(\Osiset\ShopifyApp\Util::getShopifyConfig('turbo_enabled'))
data-turbolinks-eval="false"
@endif
>
var AppBridge = window['app-bridge'];
var actions = AppBridge.actions;
var utils = AppBridge.utilities;
var createApp = AppBridge.default;
var app = createApp({
apiKey: "{{ \Osiset\ShopifyApp\Util::getShopifyConfig('api_key', $shopDomain ?? Auth::user()->name ) }}",
host: "{{ \Request::get('host') }}",
forceRedirect: true,
});
</script>

@if(\Osiset\ShopifyApp\Util::useNativeAppBridge())
@include('shopify-app::partials.token_handler')
@include('shopify-app::partials.flash_messages')
@endif

@yield('scripts')
</body>
</html>
22 changes: 0 additions & 22 deletions src/resources/views/partials/flash_messages.blade.php

This file was deleted.

30 changes: 10 additions & 20 deletions src/resources/views/partials/token_handler.blade.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
<script data-turbolinks-eval="false">
var SESSION_TOKEN_REFRESH_INTERVAL = {{ \Osiset\ShopifyApp\Util::getShopifyConfig('session_token_refresh_interval') }};
var LOAD_EVENT = '{{ \Osiset\ShopifyApp\Util::getShopifyConfig('turbo_enabled') ? 'turbolinks:load' : 'DOMContentLoaded' }}';
<script>
var SESSION_TOKEN_REFRESH_INTERVAL = {{ config('shopify-app.session_token_refresh_interval') }};
var LOAD_EVENT = 'DOMContentLoaded'
// Token updates
document.addEventListener(LOAD_EVENT, () => {
retrieveToken(app);
keepRetrievingToken(app);
retrieveToken();
keepRetrievingToken();
});
// Retrieve session token
async function retrieveToken(app) {
window.sessionToken = await utils.getSessionToken(app);
async function retrieveToken() {
window.sessionToken = await shopify.idToken();
// Update everything with the session-token class
Array.from(document.getElementsByClassName('session-token')).forEach((el) => {
if (el.hasAttribute('value')) {
el.value = window.sessionToken;
Expand All @@ -23,8 +20,8 @@
});
const bearer = `Bearer ${window.sessionToken}`;
if (window.jQuery) {
// jQuery
if (window.jQuery.ajaxSettings.headers) {
window.jQuery.ajaxSettings.headers['Authorization'] = bearer;
} else {
Expand All @@ -41,20 +38,13 @@
}
if (window.axios) {
// Axios
window.axios.defaults.headers.common['Authorization'] = bearer;
}
}
// Keep retrieving a session token periodically
function keepRetrievingToken(app) {
function keepRetrievingToken() {
setInterval(() => {
retrieveToken(app);
retrieveToken();
}, SESSION_TOKEN_REFRESH_INTERVAL);
}
document.addEventListener('turbolinks:request-start', (event) => {
var xhr = event.data.xhr;
xhr.setRequestHeader('Authorization', `Bearer ${window.sessionToken}`);
});
</script>
2 changes: 1 addition & 1 deletion tests/Traits/AuthControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function testAuthRedirectsToShopifyWhenNoCode(): void
// Check the redirect happens and location is set properly in the header.
$response->assertViewHas('shopDomain', 'example.myshopify.com');
$response->assertViewHas(
'authUrl',
'url',
'https://example.myshopify.com/admin/oauth/authorize?client_id='.Util::getShopifyConfig('api_key').'&scope=read_products%2Cwrite_products%2Cread_themes&redirect_uri=https%3A%2F%2Flocalhost%2Fauthenticate'
);

Expand Down
1 change: 1 addition & 0 deletions tests/Traits/BillingControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function testSendsShopToBillingScreen(): void

// Run the call
$response = $this->call('get', '/billing', ['shop' => $shop->getDomain()->toNative()]);

$response->assertViewHas(
'url',
'https://example.myshopify.com/admin/charges/1029266947/confirm_recurring_application_charge?signature=BAhpBANeWT0%3D--64de8739eb1e63a8f848382bb757b20343eb414f'
Expand Down
10 changes: 5 additions & 5 deletions tests/Traits/HomeControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ public function testHomeRoute(): void
$host = base64_encode($shop->getDomain()->toNative().'/admin');
$this->call('get', '/', ['token' => $this->buildToken(), 'host' => $host])
->assertOk()
->assertSee('apiKey: "'.Util::getShopifyConfig('api_key').'"', false)
->assertSee("host: \"{$host}\"", false);
->assertSee('name="shopify-api-key" content="'.Util::getShopifyConfig('api_key').'"', false)
->assertSee('https://cdn.shopify.com/shopifycloud/app-bridge.js');
}

public function testHomeRouteHostAdmin(): void
{
$shop = factory($this->model)->create(['name' => 'shop-name.myshopify.com']);
factory($this->model)->create(['name' => 'shop-name.myshopify.com']);

$host = base64_encode('admin.shopify.com/store/shop-name');
$this->call('get', '/', ['token' => $this->buildToken(), 'host' => $host])
->assertOk()
->assertSee('apiKey: "'.Util::getShopifyConfig('api_key').'"', false)
->assertSee("host: \"{$host}\"", false);
->assertSee('name="shopify-api-key" content="'.Util::getShopifyConfig('api_key').'"', false)
->assertSee('https://cdn.shopify.com/shopifycloud/app-bridge.js');
}
}

0 comments on commit 3e99d44

Please sign in to comment.