Is there any way I can access Admin API(GraphQL) in theme.liquid file using JavaScript(<script>...<script>)? - shopify

I am trying to fetch products by 'SKU', which is only possible using Admin API. I need to inject a JavaScript code snippet into theme.liquid file. Can I achieve this via JavaScript only? So far my code looks something like this:
<script>
const query = `{
productVariants(first: 1, query: "sku:<SKU>") {
edges {
node {
id
price
product {
title
description
featuredImage {
id
originalSrc
}
}
}
}
}
}`;
const STOREFRONT_ACCESS_TOKEN = 'xxxxxxxxxx';
const GRAPHQL_URL = 'https://<my-store>.myshopify.com/admin/api/2021-01/graphql.json';
const GRAPHQL_BODY = {
'method': 'POST',
'headers': {
'X-Shopify-Storefront-Access-Token': STOREFRONT_ACCESS_TOKEN,
'Content-Type': 'application/json',
},
'body': JSON.stringify({ query })
}
fetch(GRAPHQL_URL, GRAPHQL_BODY)
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
</script>
I am not very well familiar with Shopify and Shopify's APIs(Storefront, Admin). I tried every possible way but reached dead end. I would really appreciate if someone can redirect me to right resources. Thank you!

Your code looks loosely like the code from the docs here: https://shopify.dev/tutorials/authenticate-with-oauth
Two issues, really:
in this line:
'X-Shopify-Storefront-Access-Token': STOREFRONT_ACCESS_TOKEN,
you need be using a token which you get after you request it from
https://{shop}.myshopify.com/admin/oauth/access_token
the bigger issue, though, is:
to do so only through the theme code, you would ultimately have to expose your secret keys via the front end code, which is going to be a security risk. The technically correct way to use the Admin API would either be to set up a server that runs an embedded app and store those keys in a .env file there.

Related

Cypress: login through magic link error with cy.origin()

Devs at my startup have switched login to a magic link system, in which you get inside after clicking a link on the email body.
I have set up a Mailsac email to receive mails containing magic links but I haven't been able to actually follow those links because of the following:
cy.request({
method: "GET",
url: "https://mailsac.com/api/addresses/xxxx#mailsac.com/messages",
headers: {
"Mailsac-Key": "here-goes-the-key",
},
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.origin(magicLink, () => {
cy.visit('/')
});
});
I wasn't able to use cy.visit() either because the magic link URL is slightly different from the baseURL in this testing environment.
So my question is:
How could I follow this cumbersome link to find myself logged in home, or else, is there another way to deal with magic links?
Thanks
The docs say
A URL specifying the secondary origin in which the callback is to be executed. This should at the very least contain a hostname, and may also include the protocol, port number & path. Query params are not supported.
Not sure if this means the cy.visit() argument should not have query params, of just the cy.origin() parameter.
Try passing in the link
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
const magicOrigin = new URL(magicLink).origin
cy.origin(magicOrigin, { args: { magicLink } }, ({ magicLink }) => {
cy.visit(magicLink)
});
});
If that doesn't fix it, you could try using cy.request() but you'll have to observe where the token is stored after using the magicLink.
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.request(magicLink).then(response =>
const token = response??? // find out where the auth token ends up
cy.setCookie(name, value) // for example
});
});
You need to pass the domain as the first parameter to origin, and do the visit within the callback function, something like this:
const magicLinkDomain = new Url(magicLink).hostname
cy.origin(magicLinkDomain, {args: magicLink}, ({ magicLink }) => {
cy.visit(magicLink);
//...
})
Reference: https://docs.cypress.io/api/commands/origin#Usage

How to check authentication in SvelteKit?

I want to check if the user is logged in when they visit the route /login and, if so, redirect them to /. The same happens vice versa if they are not logged in as well.
I want to put something like:
export async function load() {
const res = await fetch("[endpoint]", {
headers: {
"Authorization": `Bearer ${localStorage.jwt}`
},
credentials: "include"
});
const json = await res.json();
return {
logged_in: json.logged_in
};
}
in my +page.js (or +page.server.js?) but this doesn't work as it says localStorage is undefined.
How should I go about doing this?
LocaleStorage in Sveltekit is a bit tricky, but there are ways around it, like checking to see if your code is being executed on the client's browser, like so:
import { browser } from '$app/env'
export async function load(){
if (browser) {
// Do stuff here
...
}
...
}
A solution that's worked for me is chucking this code into the <script context="module"> of a base __layout.svelte that every layout inherits from.
For instance, you could have a __layout.svelte that consists of nothing more than this code. Building off of what Stephane pointed out, if you don't want to use cookies (e.g. a jwt token with a very limited lifespan) you could use session storage;
<script context="module">
export async function load({ fetch, session }) {
const res = await fetch("[endpoint]", {
headers: {
"Authorization": `Bearer ${session.jwt}`
},
credentials: "include"
});
const json = await res.json();
return {
logged_in: json.logged_in
};
}
</script>
<slot />
(You can read more about load() here)
And then have other layouts that inherit this layout by having a layout like __layout-auth#default.svelte. You can read more about named layouts here.

Get OrderId in Order Confirmation page

I am developing an app for BigCommerce, which i have to inject a Script into Order Confirmation page.
In the script, I want to read the current order detail, so i try this
<script>
function getOrderDetail(orderId) {
return fetch("/api/storefront/order/" + orderId, {
credentials: "include",
}).then(function (response) {
return response.json();
});
}
let ORDER_ID=123;
getOrderDetail(ORDER_ID).then(data=>{
// do this ,do that,
})
</script>
I don't find any docs related to get current Order_ID, I have inspected the HTML code and tried
function getOrderId() {
const orderIdRegex = /\s+orderId:\s'(\d+)',/;
return document.body.innerHTML.match(orderIdRegex)[1];
}
I know the code may break if there is a change in UI.
In Shopify, there is a global variable window.Shopify,
I am wondering if there is a better solution, or something similar to Shopify.
Update with correct answer
Thanks #MattCoy
<script>
function getOrderDetail(){
return fetch("/api/storefront/order/{{checkout.order.id}}", {
credentials: "include",
}).then(function (response) {
return response.json();
});
}
getOrderDetail().then(data=>{
// do this ,do that,
})
</script>
If this is a script in the Script Manager, you should have access to all the Stencil theme objects via Handlebars. In this case, try {{checkout.order.id}}.

React native implement Youtube data API v3

Is it possible somehow to integrate youtube data API v3 directly in react-native?
If not, Is there any other way to do it?
Right now I am using axios to send get request but not able to figure out how exactly send the request what I tried is this:
componentWillMount() {
axios.get('https://www.googleapis.com/youtube/v3/channels', {
params: {
'id': 'UC_x5XG1OV2P6uZZ5FSM9Ttw',
'key': 'AIzaSy...',
'part': 'snippet,contentDetails,statistics'
}
}).then(
(response) => {
console.warn(response);
}).catch(
(error) => {
console.error(error);
});
}
Not sure whether it is correct or not as I don't have much experience in React native. Any help would be appreciated.
You could probably use packages like youtube-search or youtube-api-searchfor your purpose.
The call with the latter one looks like this:
import YTSearch from 'youtube-api-search';
YTSearch({ key: YOUR_API_KEY, term: YOUR_SEARCH_STRING }, result => {
console.log(result);
});

eBay's Browse API Example returns ERR_CONNECTION_RESET with GET request

I'm trying to get a selection of items from a single seller with eBay's Browse API. For the most part it works, but it seems to return an ERR_CONNECTION_RESET error when I try their example for whitelisting sellers. Their example is shown here: https://developer.ebay.com/api-docs/buy/browse/resources/item_summary/methods/search#w4-w1-w1-w0-w0-parameter-name-filter-7 (under the "You can also combine filters" heading)
I'm using this URL, which is exactly what their example is: https://api.ebay.com/buy/browse/v1/item_summary/search?q=shirt&filter=price:[10..50],sellers:{rpseller|bigSal}
and it doesn't seem to work.
I've tried many, many variations of this and nothing seems to work whenever I add the sellers filter.
This is my current code (I've removed the authorization code for obvious reasons). I'm using Aurelia, but I've tested API call elsewhere and it still doesn't seem to work.
httpClient.fetch('https://api.ebay.com/buy/browse/v1/item_summary/search?q=shirt&filter=price:[10..50],sellers:{rpseller|bigSal}', {
method:"GET",
headers: {
Authorization: "Bearer XXXXXXX"
}
})
.then(response => response.json())
.then(data => {
console.log(data);
});
Just a thought, it may be that the e-bay API requires a JSONP request instead. You could try this by replacing the JSON call using the fetch client with the aurelia-http-client. Here is an example of what this might look like with JSONP:
ebay-api.js
import {HttpClient} from 'aurelia-http-client';
import {inject} from 'aurelia-framework';
#inject(HttpClient)
export class EbayApiJSONP{
constructor(http){
this.http = http;
this.baseUrl = 'https://api.ebay.com/buy/browse/v1/item_summary/search';
this.http.configure(config => {
config.withBaseUrl(this.baseUrl);
});
}
getProductsJsonp(){
return this.http.jsonp('?q=shirt&filter=price:[10..50],sellers:{rpseller|bigSal}', 'callback')
.then(responseMessage => {
return responseMessage.response;
})
.then(results => {
return results;
});
}
}