-
-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: load all chrome web store extensions
- Loading branch information
1 parent
cee492e
commit 0a08342
Showing
4 changed files
with
239 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { createHash } from 'node:crypto' | ||
|
||
/** | ||
* Converts a normal hexadecimal string into the alphabet used by extensions. | ||
* We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a | ||
* completely numeric host, since some software interprets that as an IP address. | ||
* | ||
* @param id - The hexadecimal string to convert. This is modified in place. | ||
*/ | ||
function convertHexadecimalToIDAlphabet(id: string) { | ||
let result = '' | ||
for (const ch of id) { | ||
const val = parseInt(ch, 16) | ||
if (!isNaN(val)) { | ||
result += String.fromCharCode('a'.charCodeAt(0) + val) | ||
} else { | ||
result += 'a' | ||
} | ||
} | ||
return result | ||
} | ||
|
||
function generateIdFromHash(hash: Buffer): string { | ||
const hashedId = hash.subarray(0, 16).toString('hex') | ||
return convertHexadecimalToIDAlphabet(hashedId) | ||
} | ||
|
||
export function generateId(input: string): string { | ||
const hash = createHash('sha256').update(input, 'base64').digest() | ||
return generateIdFromHash(hash) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
packages/electron-chrome-web-store/src/browser/loader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import * as fs from 'node:fs' | ||
import * as path from 'node:path' | ||
|
||
import { generateId } from './id' | ||
|
||
const d = require('debug')('electron-chrome-web-store:loader') | ||
|
||
type ExtensionPathInfo = | ||
| { type: 'store'; manifest: chrome.runtime.Manifest; path: string; id: string } | ||
| { type: 'unpacked'; manifest: chrome.runtime.Manifest; path: string } | ||
|
||
const manifestExists = async (dirPath: string) => { | ||
if (!dirPath) return false | ||
const manifestPath = path.join(dirPath, 'manifest.json') | ||
try { | ||
return (await fs.promises.stat(manifestPath)).isFile() | ||
} catch { | ||
return false | ||
} | ||
} | ||
|
||
/** | ||
* Discover list of extensions in the given path. | ||
*/ | ||
async function discoverExtensions(extensionsPath: string): Promise<ExtensionPathInfo[]> { | ||
// Get top level directories | ||
const subDirectories = await fs.promises.readdir(extensionsPath, { | ||
withFileTypes: true, | ||
}) | ||
|
||
// Find all directories containing extension manifest.json | ||
// Limits search depth to 1-2. | ||
const extensionDirectories = await Promise.all( | ||
subDirectories | ||
.filter((dirEnt) => dirEnt.isDirectory()) | ||
.map(async (dirEnt) => { | ||
const extPath = path.join(extensionsPath, dirEnt.name) | ||
|
||
// Check if manifest exists in root directory | ||
if (await manifestExists(extPath)) { | ||
return extPath | ||
} | ||
|
||
// Check one level deeper | ||
const extSubDirs = await fs.promises.readdir(extPath, { | ||
withFileTypes: true, | ||
}) | ||
|
||
// Look for manifest in each subdirectory | ||
for (const subDir of extSubDirs) { | ||
if (!subDir.isDirectory()) continue | ||
|
||
const subDirPath = path.join(extPath, subDir.name) | ||
if (await manifestExists(subDirPath)) { | ||
return subDirPath | ||
} | ||
} | ||
}) | ||
) | ||
|
||
const results: ExtensionPathInfo[] = [] | ||
|
||
for (const extPath of extensionDirectories.filter(Boolean)) { | ||
console.log(`Loading extension from ${extPath}`) | ||
try { | ||
const manifestPath = path.join(extPath!, 'manifest.json') | ||
const manifestJson = (await fs.promises.readFile(manifestPath)).toString() | ||
const manifest: chrome.runtime.Manifest = JSON.parse(manifestJson) | ||
if (manifest.key) { | ||
results.push({ | ||
type: 'store', | ||
path: extPath!, | ||
manifest, | ||
id: generateId(manifest.key), | ||
}) | ||
} else { | ||
results.push({ | ||
type: 'unpacked', | ||
path: extPath!, | ||
manifest, | ||
}) | ||
} | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
} | ||
|
||
return results | ||
} | ||
|
||
/** | ||
* Load all extensions from the given directory. | ||
*/ | ||
export async function loadAllExtensions( | ||
session: Electron.Session, | ||
extensionsPath: string, | ||
allowUnpacked: boolean | ||
) { | ||
const extensions = await discoverExtensions(extensionsPath) | ||
d('discovered %d extension(s)', extensions.length) | ||
|
||
for (const ext of extensions) { | ||
try { | ||
if (ext.type === 'store') { | ||
const existingExt = session.getExtension(ext.id) | ||
if (existingExt) { | ||
d('skipping loading existing extension %s', ext.id) | ||
continue | ||
} | ||
d('loading extension %s', ext.id) | ||
await session.loadExtension(ext.path) | ||
} else if (allowUnpacked) { | ||
d('loading unpacked extension %s', ext.path) | ||
await session.loadExtension(ext.path) | ||
} | ||
} catch (error) { | ||
console.error(`Failed to load extension from ${ext.path}`) | ||
console.error(error) | ||
} | ||
} | ||
} |
Oops, something went wrong.