Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 85 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
"author": "HotWax Commerce",
"license": "Apache-2.0",
"dependencies": {
"@hotwax/oms-api": "^1.8.1",
"@hotwax/oms-api": "^1.26.0",
"@ionic/core": "^7.6.0",
"@ionic/vue": "^7.6.0",
"@shopify/app-bridge": "^3.7.10",
"@shopify/app-bridge-utils": "^3.5.1",
"firebase": "^10.3.1",
"luxon": "^3.3.0",
"pinia": "2.0.36",
Expand Down
105 changes: 84 additions & 21 deletions src/components/DxpLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import {
useUserStore
} from "../index"
import { DateTime } from "luxon"
import { getAppLoginUrl } from "src/utils";
import { createShopifyAppBridge, getSessionTokenFromShopify } from "src/utils";
import { loginShopifyAppUser } from "@hotwax/oms-api";
declare var process: any;

const authStore = useAuthStore()
Expand All @@ -50,18 +51,30 @@ const error = ref({
})

onMounted(async () => {
if (!Object.keys(route.query).length) {
// This will be false for when apps run in browser directly and when user first time comes from Shopify POS or Admin embedded app.
let isEmbedded = authStore.isEmbedded;

// Cases Handled:
// If the app is not embedded and there are no query params, redirect to launchpad
// If the app is embedded, it will have query params from Shopify, even if the app is not marked as embedded in the auth store, we will mark it as embedded here.
// In case if the token expired and user is routed to login path, the app was already marked as embedded, so we should not redirect to launchpad in that case.
if (!isEmbedded && !Object.keys(route.query).length) {
window.location.replace(context.appLoginUrl)
return
}

const { token, oms, expirationTime, omsRedirectionUrl, isEmbedded, shop, host} = route.query
// Update the flag in auth, since the store is updated app login url will be embedded luanchpad's url.
const isEmbeddedFlag = isEmbedded === 'true'
await handleUserFlow(token, oms, expirationTime, omsRedirectionUrl, isEmbeddedFlag, shop, host)
const { token, oms, expirationTime, omsRedirectionUrl, embedded, shop, host } = route.query
isEmbedded = isEmbedded || embedded === '1'

if (isEmbedded) {
await appBridgeLogin(shop as string, host as string);
} else {
// Update the flag in auth, since the store is updated app login url will be embedded luanchpad's url.
await handleUserFlow(token, oms, expirationTime, omsRedirectionUrl, isEmbedded, shop, host);
}
});

async function handleUserFlow(token: string, oms: string, expirationTime: string, omsRedirectionUrl = "", isEmbedded: boolean, shop: string, host: string) {
async function handleUserFlow(token: string, oms: string, expirationTime: string, omsRedirectionUrl = "", isEmbedded: boolean, shop: string, host: string, shopifyAppBridge: any = undefined) {
// fetch the current config for the user
const appConfig = loginContext.getConfig()

Expand All @@ -76,19 +89,8 @@ async function handleUserFlow(token: string, oms: string, expirationTime: string
console.error('User token has expired, redirecting to launchpad.')
error.value.message = 'User token has expired, redirecting to launchpad.'

// This will be the url of referer launchpad, we maintain two launchpads.
// The launchpad urls are defined the env file in each PW App.
// Setting this flag here because it is needed to identify the launchpad's URL, this will updated in this function later.
authStore.isEmbedded = isEmbedded
authStore.shop = shop
authStore.host = host
const appLoginUrl = getAppLoginUrl()
if (isEmbedded) {
window.location.replace(appLoginUrl)
} else {
const redirectUrl = window.location.origin + '/login' // current app URL
window.location.replace(`${appLoginUrl}?isLoggedOut=true&redirectUrl=${redirectUrl}`)
}
window.location.replace(`${context.appLoginUrl}?isLoggedOut=true&redirectUrl=${redirectUrl}`)
return
}

Expand All @@ -98,7 +100,8 @@ async function handleUserFlow(token: string, oms: string, expirationTime: string
oms,
isEmbedded,
shop: shop as any,
host: host as any
host: host as any,
shopifyAppBridge: shopifyAppBridge as any
})

context.loader.present('Logging in')
Expand Down Expand Up @@ -134,7 +137,67 @@ async function handleUserFlow(token: string, oms: string, expirationTime: string
}

function goToLaunchpad() {
window.location.replace(getAppLoginUrl())
window.location.replace(process.env.VUE_APP_LOGIN_URL)
}

async function appBridgeLogin(shop: string, host: string) {
console.log("This is an embedded app user, proceeding with Shopify App Bridge login flow.");
// In case where token expired and user is routed login path, the query params will not have shop and host,
// So we get them from auth store before it is cleared.
if (!shop) {
shop = authStore.shop
}
if (!host) {
host = authStore.host
}
if (!shop || !host) {
console.error("Shop or host is missing, cannot proceed further.");
error.value.message = "Please contact the administrator.";
return;
}
const loginPayload = {} as any;
let loginResponse;
const maargUrl = JSON.parse(process.env.VUE_APP_SHOPIFY_SHOP_CONFIG)[shop].maarg;
let shopifyAppBridge;
try {
shopifyAppBridge = await createShopifyAppBridge(shop, host);
const shopifySessionToken = await getSessionTokenFromShopify(shopifyAppBridge);
const appState: any = await shopifyAppBridge.getState();

if (!appState) {
throw new Error("Couldn't get Shopify App Bridge state, cannot proceed further.");
}
// Since the Shopify Admin doesn't provide location and user details,
// we are using the app state to get the POS location and user details in case of POS Embedded Apps.
loginPayload.sessionToken = shopifySessionToken;
if (appState.pos?.location?.id) {
loginPayload.locationId = appState.pos.location.id
}
if (appState.pos?.user?.firstName) {
loginPayload.firstName = appState.pos.user.firstName;
}
if (appState.pos?.user?.lastName) {
loginPayload.lastName = appState.pos.user.lastName;
}

loginResponse = await loginShopifyAppUser(`${maargUrl}/rest/s1/`, loginPayload);

if (!loginResponse?.token) {
throw new Error("Login response doesn't have token, cannot proceed further.");
}
} catch (e) {
console.error("Error ", e);
error.value.message = "Please contact the administrator.";
return;
}

const loginToken = loginResponse.token;
const omsInstanceUrl = loginResponse.omsInstanceUrl;
const expiresAt = loginResponse.expiresAt;
const appConfig: any = loginContext.getConfig();
// Switch Maarg and OMS URLs for Moqui first Apps
const isMoquiFirst = appConfig.systemType === "MOQUI";
await handleUserFlow(loginToken, isMoquiFirst ? maargUrl : omsInstanceUrl, expiresAt, isMoquiFirst ? omsInstanceUrl : maargUrl, true, shop, host, shopifyAppBridge);
}
</script>

Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createPinia } from "pinia";
import { useProductIdentificationStore } from "./store/productIdentification";
import { useAuthStore } from "./store/auth";
import { DxpAppVersionInfo, DxpFacilitySwitcher, DxpGitBookSearch, DxpImage, DxpLanguageSwitcher, DxpLogin, DxpMenuFooterNavigation, DxpOmsInstanceNavigator, DxpPagination, DxpProductIdentifier, DxpProductStoreSelector, DxpShopifyImg, DxpTimeZoneSwitcher, DxpUserProfile } from "./components";
import { goToOms, getProductIdentificationValue, getAppLoginUrl } from "./utils";
import { goToOms, getProductIdentificationValue, getAppLoginUrl, openPosScanner } from "./utils";
import { initialiseFirebaseApp } from "./utils/firebase"
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { createI18n } from 'vue-i18n'
Expand Down Expand Up @@ -171,5 +171,6 @@ export {
useProductIdentificationStore,
useUserStore,
userContext,
getAppLoginUrl
getAppLoginUrl,
openPosScanner
}
1 change: 1 addition & 0 deletions src/store/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const useAuthStore = defineStore('userAuth', {
isEmbedded: false,
shop: undefined,
host: undefined,
shopifyAppBridge: undefined
}
},
getters: {
Expand Down
Loading