diff --git a/.gitignore b/.gitignore index 8fb5def..b2fd10d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ config/tunnel.pid # next build output .next/ + +session.json diff --git a/hooks/useAxios.js b/hooks/useAxios.js new file mode 100644 index 0000000..1e2f61d --- /dev/null +++ b/hooks/useAxios.js @@ -0,0 +1,15 @@ +import Axios from "axios"; +import { useAppBridge } from "@shopify/app-bridge-react"; +import { getSessionToken } from "@shopify/app-bridge-utils"; + +export function useAxios() { + const app = useAppBridge(); + const instance = Axios.create(); + instance.interceptors.request.use(function (config) { + return getSessionToken(app).then((token) => { + config.headers["Authorization"] = `Bearer ${token}`; + return config; + }); + }); + return [instance]; +} diff --git a/package-lock.json b/package-lock.json index e19d225..3a3c7d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7955,6 +7955,41 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, + "co-body": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-2.0.0.tgz", + "integrity": "sha1-cwKruOiKLOCE9nyk8xYsG1SmS2c=", + "requires": { + "qs": "~2.4.1", + "raw-body": "~1.3.4" + }, + "dependencies": { + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + }, + "iconv-lite": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz", + "integrity": "sha1-xgGadZXyzvynAuq2lKAQvNkpjSA=" + }, + "qs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz", + "integrity": "sha1-9854jld33wtQENp/fE5zujJHD1o=" + }, + "raw-body": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.4.tgz", + "integrity": "sha1-zMfd/Ea3KGHN1btDPIQLcLbyf1Q=", + "requires": { + "bytes": "1.0.0", + "iconv-lite": "0.4.8" + } + } + } + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -13104,6 +13139,14 @@ } } }, + "koa-body-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/koa-body-parser/-/koa-body-parser-1.1.2.tgz", + "integrity": "sha1-Zpi/nPszuZP4AaivSahUZ8QC/GQ=", + "requires": { + "co-body": "^2.0.0" + } + }, "koa-combine-routers": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/koa-combine-routers/-/koa-combine-routers-4.0.2.tgz", @@ -13138,6 +13181,35 @@ "koa-compose": "^3.0.0" } }, + "koa-mount": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", + "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "requires": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "koa-router": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-10.0.0.tgz", @@ -13170,6 +13242,31 @@ } } }, + "koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "requires": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "koa-session": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/koa-session/-/koa-session-6.1.0.tgz", @@ -13196,6 +13293,30 @@ } } }, + "koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "requires": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -16160,6 +16281,33 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", diff --git a/package.json b/package.json index 78a97b8..e85b007 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,12 @@ "graphql": "^14.5.8", "isomorphic-fetch": "^3.0.0", "koa": "^2.13.1", + "koa-body-parser": "^1.1.2", "koa-combine-routers": "^4.0.2", + "koa-mount": "^4.0.0", "koa-router": "^10.0.0", "koa-session": "^6.1.0", + "koa-static": "^5.0.0", "next": "^10.0.8", "next-env": "^1.1.0", "node-fetch": "^2.6.1", diff --git a/pages/_app.js b/pages/_app.js index 3a42e65..9a5e845 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -7,6 +7,7 @@ import { authenticatedFetch } from "@shopify/app-bridge-utils"; import "@shopify/polaris/dist/styles.css"; import translations from "@shopify/polaris/locales/en.json"; import ClientRouter from "../components/ClientRouter"; +import createApp from "@shopify/app-bridge"; function MyProvider(props) { const app = useAppBridge(); diff --git a/pages/install.js b/pages/install.js index 8fef370..fb269f4 100644 --- a/pages/install.js +++ b/pages/install.js @@ -1,29 +1,37 @@ import { Layout, Page, SettingToggle, TextStyle } from "@shopify/polaris"; -import React, { useState } from "react"; -import Axios from "axios"; -import { getSessionToken } from "@shopify/app-bridge-utils"; +import React, { useEffect, useState } from "react"; +import { useAxios } from "../hooks/useAxios"; + function install() { - const instance = Axios.create(); - instance.interceptors.request.use(function (config) { - return getSessionToken(window.app).then((token) => { - config.headers["Authorization"] = `Bearer ${token}`; - return config; - }); - }); + const [axios] = useAxios(); const [isInstalled, setIsInstalled] = useState(null); + const [scriptTagId, setScriptTagId] = useState(); const titleDescription = isInstalled ? "Uninstall" : "Install"; const bodyDescription = isInstalled ? "installed" : "uninstalled"; + + async function fetchScriptTags() { + const { data } = await axios.get( + `https://il-shopify2.loca.lt/script_tag/all` + ); + console.log("my initial script tag status: ", data); + setIsInstalled(data.installed); + if (data.details.length > 0) { + setScriptTagId(data.details[0].id); + } + } + useEffect(() => { + fetchScriptTags(); + }, []); + async function handleAction() { if (!isInstalled) { - try { - const response = instance.post("https://il-shopify.loca.lt/script_tag"); - console.log(response.data); - } catch (err) { - console.log(err); - } + axios.post(`https://il-shopify2.loca.lt/script_tag`); + } else { + axios.delete(`https://il-shopify2.loca.lt/script_tag/?id=${scriptTagId}`); } setIsInstalled((oldValue) => !oldValue); } + return ( tag.src === src); + return matchSrc; +} + +export async function deleteScriptTagById(client, id) { + if (!client) { + console.error( + "Could not make the rest request as the client does not exist" + ); + return; } + const result = await client.delete({ + path: `script_tags/${id}`, + }); + console.log(result); + return result; } function getBaseUrl(shop) { diff --git a/server/router/script_tag.js b/server/router/script_tag.js index 46aa002..7c3f3c8 100644 --- a/server/router/script_tag.js +++ b/server/router/script_tag.js @@ -1,27 +1,33 @@ -import { verifyRequest } from "@shopify/koa-shopify-auth"; import Router from "koa-router"; -import { createScriptTag } from "../controllers/script_tag_controller"; +import { + createScriptTag, + deleteScriptTagById, + getAllScriptTags, +} from "../controllers/script_tag_controller"; const router = new Router({ prefix: "/script_tag" }); router.get("/", async (ctx) => { ctx.body = "Get script tag"; }); router.get("/all", async (ctx) => { - ctx.body = "Get all script tag"; + console.log("Get all script tag"); + const result = await getAllScriptTags(ctx.myClient, "https://google.com/"); + ctx.body = { + installed: result.length > 0, + details: result, + }; }); -router.post("/", verifyRequest(), async (ctx) => { - console.log("ctx ->", ctx); - console.log("req ->", ctx.req); - console.log("query ->", ctx.query); - console.log("state ->", ctx.state); - console.log("session ->", ctx.session); - const { shop, accessToken } = ctx.state.shopify; - await createScriptTag(shop, accessToken); +router.post("/", async (ctx) => { + console.log("create script tag", ctx.sesionFromToken); + //const { shop, accessToken } = ctx.sesionFromToken; + await createScriptTag(ctx.myClient); ctx.body = "Create a script tag"; }); router.delete("/", async (ctx) => { - ctx.body = "Delete script tag"; + const id = ctx.query.id; + const result = await deleteScriptTagById(ctx.myClient, id); + ctx.body = result; }); export default router; diff --git a/server/server.js b/server/server.js index fde0925..58ea64f 100644 --- a/server/server.js +++ b/server/server.js @@ -8,26 +8,30 @@ import next from "next"; import Router from "koa-router"; import fs from "fs"; import routes from "./router/index"; - import { Session } from "@shopify/shopify-api/dist/auth/session"; + dotenv.config(); const port = parseInt(process.env.PORT, 10) || 8081; const dev = process.env.NODE_ENV !== "production"; const app = next({ dev, }); -const handle = app.getRequestHandler(); +const handle = app.getRequestHandler(); +const FILENAME = "./session.json"; function storeCallback(session) { console.log("storeCallback "); - fs.writeFileSync("./session.json", JSON.stringify(session)); + fs.writeFileSync(FILENAME, JSON.stringify(session)); return true; } function loadCallback(id) { console.log("loadCallback "); - const sessionResult = fs.readFileSync("./session.json", "utf8"); - return Object.assign(new Session(), JSON.parse(sessionResult)); + if (fs.existsSync(FILENAME)) { + const sessionResult = fs.readFileSync(FILENAME, "utf8"); + return Object.assign(new Session(), JSON.parse(sessionResult)); + } + return false; } function deleteCallback(id) { @@ -69,7 +73,6 @@ app.prepare().then(async () => { async afterAuth(ctx) { // Access token and shop available in ctx.state.shopify const { shop, accessToken, scope } = ctx.state.shopify; - console.log("scope", scope); ACTIVE_SHOPIFY_SHOPS[shop] = scope; const response = await Shopify.Webhooks.Registry.register({ @@ -119,16 +122,23 @@ app.prepare().then(async () => { } }); - router.post("/script_tag", async (ctx) => { - console.log("ctx ->", ctx); - console.log("req ->", ctx.req); - console.log("query ->", ctx.query); - console.log("state ->", ctx.state); - console.log("session ->", ctx.session); - const { shop, accessToken } = ctx.state.shopify; - await createScriptTag(shop, accessToken); - ctx.body = "Create a script tag"; - }); + async function injectSession(ctx, next) { + const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res); + ctx.sesionFromToken = session; + console.log("s", session); + if (session?.shop && session?.accessToken) { + const client = new Shopify.Clients.Rest( + session.shop, + session.accessToken + ); + ctx.myClient = client; + } + return next(); + } + + server.use(injectSession); + server.use(routes()); + router.get("(/_next/static/.*)", handleRequest); // Static content is clear router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions diff --git a/session.json b/session.json new file mode 100644 index 0000000..2a854b4 --- /dev/null +++ b/session.json @@ -0,0 +1,25 @@ +{ + "id": "fe3b49d1-72ca-4111-b6cd-bddc409e2f67", + "shop": "swiftnextstep.myshopify.com", + "state": "650961563386916", + "isOnline": true, + "accessToken": "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", + "expires": "2021-03-14T05:58:56.078Z", + "scope": "write_products,write_customers,write_draft_orders,write_script_tags", + "onlineAccessInfo": { + "expires_in": 86399, + "associated_user_scope": "write_products,write_customers,write_draft_orders,write_script_tags", + "session": "7ec6dfa172566f599e6c5be6fc754c251a5b98b9ce43fabbc6fdfefa37a2d81f", + "account_number": 0, + "associated_user": { + "id": 71207485609, + "first_name": "Icaro", + "last_name": "Lavrador", + "email": "swiftnextstep@gmail.com", + "account_owner": true, + "locale": "en-NZ", + "collaborator": false, + "email_verified": true + } + } +}