Skip to content

Commit

Permalink
Merge pull request #30 from isinstock/browser-extension-installs
Browse files Browse the repository at this point in the history
Browser extension installs
  • Loading branch information
dewski authored Dec 16, 2023
2 parents d40b1fa + 1ac7076 commit 2edeab4
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 20 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"**/node_modules": true,
"**/dist": true
},
"jest.autoRun": "off",
"eslint.validate": ["javascript", "javascriptreact", "html", "typescript", "typescriptreact"],
"eslint.alwaysShowStatus": true,
"eslint.lintTask.enable": true,
Expand All @@ -40,6 +41,6 @@
"javascript.preferences.quoteStyle": "single",
"javascript.preferences.importModuleSpecifier": "relative",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
}
}
1 change: 0 additions & 1 deletion build.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ const config = {
minify: isProduction,
target: ['ios15', 'chrome100', 'edge100', 'firefox100', 'safari15'],
define: {
VERSION: `"${pkg.version}"`,
ISINSTOCK_URL: isProduction ? '"https://isinstock.com"' : '"http://localhost:3000"',
CHROME_EXTENSION_ID: '"bnglflgcpflggbpbcbpgeaknekceeojd"',
},
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "isinstock-extension",
"private": true,
"version": "0.1.3",
"version": "0.1.4",
"prettier": "@github/prettier-config",
"eslintConfig": {
"extends": "preact"
Expand Down
16 changes: 8 additions & 8 deletions safari/Is In Stock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "Safari Extension/Is In Stock Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = PB45PKVTW4;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -446,7 +446,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.4;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
Expand All @@ -464,7 +464,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "Safari Extension/Is In Stock Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = PB45PKVTW4;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -477,7 +477,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.4;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
Expand All @@ -499,7 +499,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS/Is In Stock.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = PB45PKVTW4;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -514,7 +514,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.1.0;
MARKETING_VERSION = 0.1.4;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
Expand All @@ -537,7 +537,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS/Is In Stock.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = PB45PKVTW4;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -552,7 +552,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.1.0;
MARKETING_VERSION = 0.1.4;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
Expand Down
Binary file not shown.
1 change: 0 additions & 1 deletion src/@types/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
declare const VERSION: string
declare const ISINSTOCK_URL: string
declare const CHROME_EXTENSION_ID: string

Expand Down
6 changes: 2 additions & 4 deletions src/__tests__/amazon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,10 @@ describe('Browser Extension Test', () => {
test('popstate to restore page searches for products', async () => {
await page.goto('https://www.amazon.com/dp/B08G58D42M/')
await page.waitForSelector('#isinstock-button')

await page.goto('https://www.amazon.com/')
await page.goBack()
const element = await page.waitForSelector('#isinstock-button')

const element = await page.waitForSelector('#isinstock-button')
expect(element).not.toBe(null)
})

Expand All @@ -166,8 +165,7 @@ describe('Browser Extension Test', () => {
})

await page.goto('https://www.amazon.com/dp/B088HH6LW5/')
await page.click('[data-dp-url] button')

await page.click(`[data-dp-url] button, [data-asin='B0BLF2RWNV'] input.a-button-input`)
await page.waitForRequest(request => isValidationRequest(request))

expect(interceptedValidationsRequests).toStrictEqual([
Expand Down
49 changes: 49 additions & 0 deletions src/api/browser-extension-install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fetchApi from '../utils/fetch-api'
import {FetchError} from '../utils/fetch-error'

export type BrowserExtensionInstallResponseToken = {
token: string
}

export type BrowserExtensionInstallResponseError = {
error: string
}

export type BrowserExtensionInstallResponse =
| BrowserExtensionInstallResponseToken
| BrowserExtensionInstallResponseError

export async function createBrowserExtensionInstall(version: string): Promise<BrowserExtensionInstallResponseToken> {
const body = JSON.stringify({version})
const response = await fetchApi('/api/browser-extension-installs', 'POST', body)
if (!response.ok) {
throw new FetchError(response)
}

return (await response.json()) as BrowserExtensionInstallResponseToken
}

export async function updateBrowserExtensionInstall(
token: string,
version: string,
reason: string,
): Promise<BrowserExtensionInstallResponseToken> {
const path = `/api/browser-extension-installs/${token}`
const body = JSON.stringify({version, reason})
const response = await fetchApi(path, 'PATCH', body)
if (!response.ok) {
throw new FetchError(response)
}

return (await response.json()) as BrowserExtensionInstallResponseToken
}

export async function browserExtensionStartup(token: string): Promise<boolean> {
const path = `/api/browser-extension-installs/${token}/startup`
const response = await fetchApi(path, 'POST')
if (!response.ok) {
throw new FetchError(response)
}

return true
}
48 changes: 48 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import browser from 'webextension-polyfill'

import {InventoryStateNormalized} from './@types/inventory-states'
import {Message, MessageAction} from './@types/messages'
import {
browserExtensionStartup,
createBrowserExtensionInstall,
updateBrowserExtensionInstall,
} from './api/browser-extension-install'
import {getBrowserExtensionInstallToken, setBrowserExtensionInstallToken} from './utils/browser-extension-install-token'
import {FetchError} from './utils/fetch-error'

// As browser navigation changes, inform the content script as a hook for certain retailers to perform custom querying.
const loadedTabs = new Map<number, boolean>()
Expand All @@ -22,6 +29,47 @@ browser.tabs.onUpdated.addListener(
},
)

browser.runtime.onStartup.addListener(async () => {
try {
const token = await getBrowserExtensionInstallToken()
if (token !== '') {
await browserExtensionStartup(token)
}
} catch (error) {
if (error instanceof FetchError && error.status === 404) {
console.debug('Browser extension install not found')
} else {
throw error
}
}
})

// Register the install
browser.runtime.onInstalled.addListener(async ({reason}) => {
try {
const existingToken = await getBrowserExtensionInstallToken()
if (existingToken !== '') {
await updateBrowserExtensionInstall(existingToken, browser.runtime.getManifest().version, reason)
return
}
} catch (error) {
if (error instanceof FetchError && error.status === 404) {
console.debug('Browser extension install not found, creating new one')
} else {
throw error
}
}

try {
const manifest = browser.runtime.getManifest()
const version = manifest.version
const {token} = await createBrowserExtensionInstall(version)
await setBrowserExtensionInstallToken(token)
} catch (e) {
console.debug('Error creating browser extension install', e)
}
})

// Receives messages from content scripts
browser.runtime.onMessage.addListener(({action, value}: Message, _sender) => {
if (action === MessageAction.InventoryState) {
Expand Down
13 changes: 13 additions & 0 deletions src/utils/browser-extension-install-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import browser from 'webextension-polyfill'

export async function setBrowserExtensionInstallToken(browserExtensionInstallToken: string) {
await browser.storage.sync.set({browserExtensionInstallToken})
}

export async function getBrowserExtensionInstallToken(): Promise<string> {
const {browserExtensionInstallToken} = await browser.storage.sync.get({
browserExtensionInstallToken: '',
})

return browserExtensionInstallToken
}
13 changes: 11 additions & 2 deletions src/utils/fetch-api.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import browser from 'webextension-polyfill'

import {getBrowserExtensionInstallToken} from './browser-extension-install-token'

const browserExtensionInstallTokenHeader = 'X-Browser-Extension-Install-Token'
const browserExtensionVersionHeader = 'X-Browser-Extension-Version'
const manifest = browser.runtime.getManifest()
const version = manifest.version

const fetchApi = async (
path: string,
method: 'POST' | 'GET',
method: 'POST' | 'GET' | 'PUT' | 'PATCH' | 'DELETE',
body?: BodyInit | null | undefined,
signal?: AbortSignal | null | undefined,
) => {
const {accessToken} = await browser.storage.local.get({
accessToken: '',
})
const browserExtensionInstallToken = await getBrowserExtensionInstallToken()

const headers: HeadersInit = {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-Extension-Version': VERSION,
[browserExtensionVersionHeader]: version,
[browserExtensionInstallTokenHeader]: browserExtensionInstallToken,
}
if (accessToken !== '' && accessToken !== null) {
headers['Authorization'] = `Bearer ${accessToken}`
Expand Down
12 changes: 12 additions & 0 deletions src/utils/fetch-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class FetchError extends Error {
status: number
response: Response

constructor(response: Response) {
super(`Could not fetch ${response.url} (${response.status})`)

this.status = response.status
this.response = response
this.name = 'FetchError'
}
}

0 comments on commit 2edeab4

Please sign in to comment.