-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy pathadmin.ts
153 lines (140 loc) · 4.32 KB
/
admin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import {graphqlRequest, GraphQLVariables} from './graphql.js'
import {AdminSession} from '../session.js'
import {outputContent, outputToken} from '../../../public/node/output.js'
import {BugError, AbortError} from '../error.js'
import {restRequestBody, restRequestHeaders, restRequestUrl} from '../../../private/node/api/rest.js'
import {fetch} from '../http.js'
import {ClientError, gql} from 'graphql-request'
/**
* Executes a GraphQL query against the Admin API.
*
* @param query - GraphQL query to execute.
* @param session - Shopify admin session including token and Store FQDN.
* @param variables - GraphQL variables to pass to the query.
* @returns The response of the query of generic type <T>.
*/
export async function adminRequest<T>(query: string, session: AdminSession, variables?: GraphQLVariables): Promise<T> {
const api = 'Admin'
const version = await fetchApiVersion(session)
const url = adminUrl(session.storeFqdn, version)
return graphqlRequest({query, api, url, token: session.token, variables})
}
/**
* GraphQL query to retrieve the latest supported API version.
*
* @param session - Shopify admin session including token and Store FQDN.
* @returns - The latest supported API version.
*/
async function fetchApiVersion(session: AdminSession): Promise<string> {
const url = adminUrl(session.storeFqdn, 'unstable')
const query = apiVersionQuery()
try {
const data: ApiVersionResponse = await graphqlRequest({
query,
api: 'Admin',
url,
token: session.token,
variables: {},
responseOptions: {handleErrors: false},
})
return data.publicApiVersions
.filter((item) => item.supported)
.map((item) => item.handle)
.sort()
.reverse()[0]!
} catch (error) {
if (error instanceof ClientError && error.response.status === 403) {
const storeName = session.storeFqdn.replace('.myshopify.com', '')
throw new AbortError(
outputContent`Looks like you don't have access this dev store: (${outputToken.link(
storeName,
`https://${session.storeFqdn}`,
)})`,
outputContent`If you're not the owner, create a dev store staff account for yourself`,
)
}
throw new BugError(`Unknown error connecting to your store`)
}
}
/**
* Returns the Admin API URL for the given store and version.
*
* @param store - Store FQDN.
* @param version - API version.
* @returns - Admin API URL.
*/
function adminUrl(store: string, version: string | undefined): string {
const realVersion = version || 'unstable'
return `https://${store}/admin/api/${realVersion}/graphql.json`
}
interface ApiVersionResponse {
publicApiVersions: {handle: string; supported: boolean}[]
}
/**
* GraphQL query string to retrieve the latest supported API version.
*
* @returns - A query string.
*/
function apiVersionQuery(): string {
return gql`
query {
publicApiVersions {
handle
supported
}
}
`
}
/**
* Executes a REST request against the Admin API.
*
* @param method - Request's HTTP method.
* @param path - Path of the REST resource.
* @param session - Shopify Admin session including token and Store FQDN.
* @param requestBody - Request body of including REST resource specific parameters.
* @param searchParams - Search params, appended to the URL.
* @param apiVersion - Admin API version.
* @returns - The {@link RestResponse}.
*/
export async function restRequest<T>(
method: string,
path: string,
session: AdminSession,
requestBody?: T,
searchParams: {[name: string]: string} = {},
apiVersion = 'unstable',
): Promise<RestResponse> {
const url = restRequestUrl(session, apiVersion, path, searchParams)
const body = restRequestBody<T>(requestBody)
const headers = restRequestHeaders(session)
const response = await fetch(url, {
headers,
method,
body,
})
const json = await response.json().catch(() => ({}))
return {
json,
status: response.status,
headers: response.headers.raw(),
}
}
/**
* Respose of a REST request.
*/
export interface RestResponse {
/**
* REST JSON respose.
*/
// Using `any` to avoid introducing extra DTO layers.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
json: any
/**
* HTTP response status.
*/
status: number
/**
* HTTP response headers.
*/
headers: {[key: string]: string[]}
}