generated from PlasmoHQ/qtt
-
Notifications
You must be signed in to change notification settings - Fork 7
/
index.ts
149 lines (121 loc) · 3.82 KB
/
index.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
import { ReadStream, createReadStream } from "fs"
import got from "got"
const baseAPIUrl = "https://www.googleapis.com"
const refreshTokenURI = `${baseAPIUrl}/oauth2/v4/token`
export type Options = {
extId: string
clientId: string
clientSecret: string
refreshToken: string
uploadOnly?: boolean
}
export type PublishTarget = "default" | "trustedTesters"
export type GetProjection = "DRAFT" | "PUBLISHED"
export type UploadState = "FAILURE" | "IN_PROGRESS" | "NOT_FOUND" | "SUCCESS"
export const errorMap = {
extId:
"No extension ID provided, e.g. https://chrome.google.com/webstore/detail/EXT_ID",
clientId:
"No client ID provided. To get one: https://github.com/PlasmoHQ/chrome-webstore-api/blob/main/token.md",
clientSecret:
"No client secret provided. To get one: https://github.com/PlasmoHQ/chrome-webstore-api/blob/main/token.md",
refreshToken:
"No refresh token provided. To get one: https://github.com/PlasmoHQ/chrome-webstore-api/blob/main/token.md"
}
export const requiredFields = Object.keys(errorMap)
export class ChromeWebstoreAPI {
options = {} as Options
constructor(options: Options) {
for (const field of requiredFields) {
if (!options[field]) {
throw new Error(errorMap[field])
}
}
this.options = { ...options }
}
get uploadEndpoint() {
return `${baseAPIUrl}/upload/chromewebstore/v1.1/items/${this.options.extId}`
}
getPublishEndpoint(target: PublishTarget) {
return `${baseAPIUrl}/chromewebstore/v1.1/items/${this.options.extId}/publish?publishTarget=${target}`
}
getInfoEndpoint(projection: string) {
return `${baseAPIUrl}/chromewebstore/v1.1/items/${this.options.extId}?projection=${projection}`
}
async submit({ filePath = "", target = "default" as PublishTarget }) {
const accessToken = await this.getAccessToken()
const { uploadState, itemError } = await this.upload(
{
readStream: createReadStream(filePath)
},
accessToken
)
if (uploadState === "FAILURE" || uploadState === "NOT_FOUND") {
throw new Error(
itemError.map(({ error_detail }) => error_detail).join("\n")
)
}
if (this.options.uploadOnly) {
return
}
return this.publish(
{
target
},
accessToken
)
}
async upload({ readStream = null as ReadStream }, _accessToken = "") {
if (!readStream) {
throw new Error("Read stream missing")
}
const accessToken = _accessToken || (await this.getAccessToken())
return got
.put(this.uploadEndpoint, {
headers: this.getHeaders(accessToken),
body: readStream,
throwHttpErrors: false
})
.json<{
uploadState: UploadState
itemError: Array<Record<string, string>>
}>()
}
async publish({ target = "default" as PublishTarget }, _accessToken = "") {
const accessToken = _accessToken || (await this.getAccessToken())
return got
.post(this.getPublishEndpoint(target), {
headers: this.getHeaders(accessToken)
})
.json()
}
async get({ projection = "DRAFT" as GetProjection }, _accessToken = "") {
const accessToken = _accessToken || (await this.getAccessToken())
return got
.get(this.getInfoEndpoint(projection), {
headers: this.getHeaders(accessToken)
})
.json()
}
async getAccessToken() {
const response = await got
.post(refreshTokenURI, {
json: {
client_id: this.options.clientId,
refresh_token: this.options.refreshToken,
grant_type: "refresh_token",
client_secret: this.options.clientSecret
}
})
.json<{
access_token: string
}>()
return response.access_token
}
getHeaders(token: string) {
return {
Authorization: `Bearer ${token}`,
"x-goog-api-version": "2"
}
}
}