AMO (addons.mozilla.org) integration for automated extension signing and publishing with JWT authentication, upload management, and approval workflows.
Sign extensions for distribution through Firefox with Mozilla's signing infrastructure.
/**
* Sign an extension with Mozilla's AMO signing service
* @param params - Signing configuration parameters
* @param options - Optional dependencies and settings
* @returns Promise resolving to signing result
*/
function sign(params: SignParams, options?: SignOptions): Promise<SignResult>;
interface SignParams {
/** Source directory containing the extension */
sourceDir: string;
/** Directory where signed extension will be saved */
artifactsDir: string;
/** AMO API key (JWT issuer) */
apiKey: string;
/** AMO API secret (JWT secret) */
apiSecret: string;
/** Signing channel: 'listed' or 'unlisted' */
channel: 'listed' | 'unlisted';
/** AMO API base URL */
amoBaseUrl?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Approval polling timeout in milliseconds */
approvalTimeout?: number;
/** HTTP proxy for API requests */
apiProxy?: string;
/** Path to JSON file with AMO metadata */
amoMetadata?: string;
/** Path to source code archive for review */
uploadSourceCode?: string;
/** Files to ignore during packaging */
ignoreFiles?: string[];
/** web-ext version for user agent */
webextVersion?: string;
}
interface SignOptions {
/** Custom build function */
build?: Function;
/** Pre-validated manifest data */
preValidatedManifest?: object;
/** Custom addon submission function */
submitAddon?: Function;
/** File system read function */
asyncFsReadFile?: Function;
}
interface SignResult {
/** Whether signing was successful */
success: boolean;
/** Extension ID assigned by Mozilla */
id?: string;
/** Paths to downloaded signed files */
downloadedFiles?: string[];
}Usage Examples:
import { cmd } from "web-ext";
// Basic signing for unlisted distribution
const result = await cmd.sign({
sourceDir: './my-extension',
artifactsDir: './web-ext-artifacts',
apiKey: 'user:12345678:987',
apiSecret: 'your-api-secret-here',
channel: 'unlisted'
});
console.log('Signing successful:', result.success);
console.log('Extension ID:', result.id);
console.log('Downloaded files:', result.downloadedFiles);
// Signing with metadata and source code upload
await cmd.sign({
sourceDir: './extension',
artifactsDir: './signed',
apiKey: process.env.AMO_API_KEY,
apiSecret: process.env.AMO_API_SECRET,
channel: 'listed',
amoMetadata: './metadata.json',
uploadSourceCode: './source-code.zip',
timeout: 900000, // 15 minutes
approvalTimeout: 3600000 // 1 hour
});Advanced client for direct interaction with Mozilla's addon submission API.
class Client {
constructor(options: ClientOptions);
/** Upload XPI file to AMO */
doUploadSubmit(xpiPath: string, channel: string): Promise<UploadResult>;
/** Wait for upload validation to complete */
waitForValidation(uuid: string): Promise<ValidationResult>;
/** Submit new addon to AMO */
doNewAddonSubmit(uuid: string, metaDataJson?: object): Promise<AddonResult>;
/** Submit new version of existing addon */
doNewAddonOrVersionSubmit(addonId: string, uuid: string, metaDataJson?: object): Promise<VersionResult>;
/** Wait for addon approval */
waitForApproval(addonId: string, versionId: string, editUrl: string): Promise<ApprovalResult>;
/** Download signed file from AMO */
downloadSignedFile(fileUrl: string, addonId: string): Promise<string>;
/** Generate content hash for XPI file */
hashXpiCrcs(filePath: string, asyncFsReadFile?: Function): Promise<string>;
}
interface ClientOptions {
/** AMO API base URL */
apiUrlPrefix: string;
/** JWT authentication handler */
apiAuth: JwtApiAuth;
/** HTTP proxy configuration */
apiProxy?: string;
/** Validation check timeout */
validationCheckTimeout: number;
/** Approval check timeout */
approvalCheckTimeout: number;
/** User agent string for requests */
userAgentString: string;
}Secure authentication system for AMO API access.
class JwtApiAuth {
constructor(options: JwtAuthOptions);
/** Generate signed JWT token */
signJWT(): string;
/** Get authorization header for API requests */
getAuthHeader(): string;
}
interface JwtAuthOptions {
/** API key (JWT issuer) from AMO */
apiKey: string;
/** API secret (JWT secret) from AMO */
apiSecret: string;
/** JWT expiration time in seconds */
apiJwtExpiresIn?: number;
}Usage Example:
import { JwtApiAuth, Client } from "web-ext/util/submit-addon";
const auth = new JwtApiAuth({
apiKey: 'user:12345678:987',
apiSecret: 'your-secret-key',
apiJwtExpiresIn: 300 // 5 minutes
});
const client = new Client({
apiUrlPrefix: 'https://addons.mozilla.org/api/v5/',
apiAuth: auth,
validationCheckTimeout: 600000, // 10 minutes
approvalCheckTimeout: 3600000, // 1 hour
userAgentString: 'web-ext/8.9.0'
});Functions for managing upload state and caching.
/**
* Save extension ID to file for future submissions
* @param filePath - Path to save ID file
* @param id - Extension ID to save
* @returns Promise that resolves when file is saved
*/
function saveIdToFile(filePath: string, id: string): Promise<void>;
/**
* Save upload UUID and metadata for resume capability
* @param filePath - Path to save UUID file
* @param data - Upload data to save
* @returns Promise that resolves when file is saved
*/
function saveUploadUuidToFile(filePath: string, data: UploadData): Promise<void>;
/**
* Retrieve saved upload UUID from file
* @param filePath - Path to UUID file
* @param asyncFsReadFile - File read function
* @returns Promise resolving to saved upload data
*/
function getUploadUuidFromFile(filePath: string, asyncFsReadFile?: Function): Promise<UploadData>;
interface UploadData {
/** Upload UUID from AMO */
uuid: string;
/** Upload metadata */
metadata: object;
/** Upload timestamp */
timestamp: number;
}The signing process follows a specific workflow through various states:
interface UploadResult {
/** Unique upload identifier */
uuid: string;
/** Upload processing status */
processed: boolean;
/** Validation errors if any */
validation?: ValidationErrors;
}
interface ValidationResult {
/** Validation completion status */
valid: boolean;
/** Validation errors and warnings */
errors: ValidationMessage[];
/** Validation metadata */
metadata: object;
}
interface AddonResult {
/** Assigned addon ID */
id: string;
/** Addon status */
status: string;
/** Review URL */
editUrl: string;
}
interface VersionResult {
/** Version ID */
id: string;
/** Version status */
status: string;
/** Download URL for signed file */
fileUrl?: string;
}
interface ApprovalResult {
/** Approval status */
approved: boolean;
/** Final download URL */
downloadUrl: string;
/** Approval metadata */
metadata: object;
}Configure addon metadata for AMO submission.
interface AMOMetadata {
/** Addon categories */
categories?: string[];
/** License identifier */
license?: string;
/** Support URL */
support_url?: string;
/** Homepage URL */
homepage?: string;
/** Summary description */
summary?: string;
/** Detailed description */
description?: string;
/** Developer comments for reviewers */
developer_comments?: string;
/** Privacy policy URL */
privacy_policy?: string;
}Metadata File Example (metadata.json):
{
"categories": ["productivity", "tools"],
"license": "MIT",
"support_url": "https://github.com/user/extension/issues",
"homepage": "https://github.com/user/extension",
"summary": "A useful browser extension for productivity",
"description": "This extension helps users be more productive by...",
"developer_comments": "This version fixes several bugs and adds new features.",
"privacy_policy": "https://example.com/privacy"
}Default file system functions used by the signing system.
/**
* Default async file read function (alias to fs.promises.readFile)
* Used for reading files during the signing process
*/
const defaultAsyncFsReadFile: Function;/** Default filename for storing extension ID */
const extensionIdFile: string = ".web-extension-id";
/** Default filename for storing upload UUID */
const uploadUuidFile: string = ".amo-upload-uuid";The signing system handles various error conditions:
Error Recovery:
The signing system supports resuming interrupted operations:
Usage Example with Error Handling:
try {
const result = await cmd.sign({
sourceDir: './extension',
artifactsDir: './signed',
apiKey: process.env.AMO_API_KEY,
apiSecret: process.env.AMO_API_SECRET,
channel: 'unlisted',
timeout: 600000
});
if (result.success) {
console.log('Extension signed successfully!');
console.log('Extension ID:', result.id);
console.log('Downloaded files:', result.downloadedFiles);
}
} catch (error) {
if (error.code === 'VALIDATION_ERROR') {
console.error('Extension validation failed:', error.message);
} else if (error.code === 'AUTH_ERROR') {
console.error('Authentication failed. Check your API keys.');
} else {
console.error('Signing failed:', error.message);
}
}