Skip to content

Commit

Permalink
Convert to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante committed Jan 14, 2024
1 parent 57f210c commit cdaa305
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ npm-debug.log
web-ext-artifacts
.env
test/extension/manifest.json
index.js
index.d.ts
70 changes: 45 additions & 25 deletions index.js → index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,56 @@
/* eslint-disable @typescript-eslint/naming-convention */
// API documentation:
// https://developer.chrome.com/docs/webstore/api
// https://developer.chrome.com/docs/webstore/using-api

import { type JsonObject } from 'type-fest';

const rootURI = 'https://www.googleapis.com';
export const refreshTokenURI = 'https://www.googleapis.com/oauth2/v4/token';
const uploadExistingURI = id =>
const uploadExistingURI = (id: string) =>
`${rootURI}/upload/chromewebstore/v1.1/items/${id}`;
const publishURI = (id, target) =>
const publishURI = (id: string, target: string) =>
`${rootURI}/chromewebstore/v1.1/items/${id}/publish?publishTarget=${target}`;
const getURI = (id, projection) =>
const getURI = (id: string, projection: string) =>
`${rootURI}/chromewebstore/v1.1/items/${id}?projection=${projection}`;

const requiredFields = ['extensionId', 'clientId', 'refreshToken'];
const requiredFields = ['extensionId', 'clientId', 'refreshToken'] as const;

export type APIClientOptions = {
extensionId: string;
clientId: string;
refreshToken: string;
clientSecret: string | undefined;
};

class APIClient {
constructor(options) {
extensionId: string;
clientId: string;
refreshToken: string;
clientSecret: string | undefined;

constructor(options: APIClientOptions) {
if (typeof fetch !== 'function') {
throw new TypeError('`chrome-webstore-upload` requires Node.js 18.0 or newer because it relies on the global `fetch` function.');
throw new TypeError('`chrome-webstore-upload` requires Node.js 18.17 or newer because it relies on the global `fetch` function.');
}

if (typeof options !== 'object') {
throw new TypeError('The options object is required');
}

for (const field of requiredFields) {
if (!options[field]) {
throw new Error(`Option "${field}" is required`);
}

this[field] = options[field];
}

if ('clientSecret' in options) {
this.clientSecret = options.clientSecret;
}
this.extensionId = options.extensionId;
this.clientId = options.clientId;
this.refreshToken = options.refreshToken;
this.clientSecret = options.clientSecret;
}

async uploadExisting(readStream, token = this.fetchToken()) {
async uploadExisting(readStream: ReadableStream, token = this.fetchToken()): Promise<JsonObject> {
if (!readStream) {
throw new Error('Read stream missing');
}
Expand All @@ -42,45 +60,47 @@ class APIClient {
const request = await fetch(uploadExistingURI(extensionId), {
method: 'PUT',
headers: this._headers(await token),
// @ts-expect-error Node extension? 🤷‍♂️ Required
duplex: 'half',
body: readStream,
});

return request.json();
return request.json() as Promise<JsonObject>;
}

async publish(target = 'default', token = this.fetchToken()) {
async publish(target = 'default', token = this.fetchToken()): Promise<JsonObject> {
const { extensionId } = this;

const request = await fetch(publishURI(extensionId, target), {
method: 'POST',
headers: this._headers(await token),
});

return request.json();
return request.json() as Promise<JsonObject>;
}

async get(projection = 'DRAFT', token = this.fetchToken()) {
async get(projection = 'DRAFT', token = this.fetchToken()): Promise<JsonObject> {
const { extensionId } = this;

const request = await fetch(getURI(extensionId, projection), {
method: 'GET',
headers: this._headers(await token),
});

return request.json();
return request.json() as Promise<JsonObject>;
}

async fetchToken() {
async fetchToken(): Promise<string> {
const { clientId, clientSecret, refreshToken } = this;
const json = {
client_id: clientId,
refresh_token: refreshToken,
grant_type: 'refresh_token',
client_secret: clientSecret,
};

if (clientSecret) {
json.client_secret = clientSecret;
if (!clientSecret) {
delete json.client_secret;
}

const request = await fetch(refreshTokenURI, {
Expand All @@ -91,19 +111,19 @@ class APIClient {
},
});

const response = await request.json();
const response = await request.json() as JsonObject;

return response.access_token;
return response['access_token'] as string;
}

_headers(token) {
_headers(token: string): { Authorization: string; 'x-goog-api-version': string } {
return {
Authorization: `Bearer ${token}`,
'x-goog-api-version': '2',
};
}
}

export default function chromeWebstoreUpload(...args) {
return new APIClient(...args);
export default function chromeWebstoreUpload(options: APIClientOptions) {
return new APIClient(options);
}
31 changes: 26 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@
],
"type": "module",
"exports": "./index.js",
"types": "./index.d.ts",
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"scripts": {
"test": "xo && vitest run",
"upload": "npm run bundle && npm run test:upload",
"prebundle": "dot-json test/extension/manifest.json version $(utc-version)",
"bundle": "web-ext build --filename live-test.zip --overwrite-dest",
"postbundle": "git restore test/extension/manifest.json",
"test:upload": "eval $(cat .env) node test/live-test.js"
"build": "tsc",
"watch": "tsc -w",
"prepack": "npm run build",
"test": "xo && vitest run && tsc",
"test:upload": "eval $(cat .env) node test/live-test.js",
"upload": "npm run bundle && npm run test:upload"
},
"xo": {
"rules": {
Expand All @@ -40,14 +45,30 @@
"always"
]
},
"overrides": [
{
"files": [
"*.ts"
],
"rules": {
"@typescript-eslint/object-curly-spacing": [
"error",
"always"
]
}
}
],
"space": 4
},
"devDependencies": {
"@sindresorhus/tsconfig": "^5.0.0",
"dot-json": "^1.3.0",
"fetch-mock": "^9.11.0",
"node-fetch": "^2.7.0",
"type-fest": "^4.9.0",
"typescript": "^5.3.3",
"utc-version": "^2.0.2",
"vitest": "^1.0.4",
"vitest": "^1.2.0",
"xo": "^0.56.0"
},
"engines": {
Expand Down
9 changes: 9 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@sindresorhus/tsconfig",
"compilerOptions": {
"outDir": ".",
},
"files": [
"index.ts"
]
}

0 comments on commit cdaa305

Please sign in to comment.