or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

android-development.mdbuild-system.mdextension-runners.mdindex.mdlinting-system.mdlogging-system.mdmain-api.mdsigning-publishing.md
tile.json

signing-publishing.mddocs/

Signing and Publishing

AMO (addons.mozilla.org) integration for automated extension signing and publishing with JWT authentication, upload management, and approval workflows.

Capabilities

Sign Function

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
});

AMO Client

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;
}

JWT Authentication

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'
});

Upload Management

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;
}

Signing Workflow States

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;
}

Metadata Configuration

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"
}

File System Utilities

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;

Constants and Files

/** Default filename for storing extension ID */
const extensionIdFile: string = ".web-extension-id";

/** Default filename for storing upload UUID */
const uploadUuidFile: string = ".amo-upload-uuid";

Error Handling

The signing system handles various error conditions:

  • Authentication errors: Invalid API keys, expired tokens
  • Upload errors: File corruption, size limits, network issues
  • Validation errors: Extension doesn't meet AMO requirements
  • API errors: Rate limiting, service unavailability
  • Approval timeouts: Extension review takes longer than expected

Error Recovery:

The signing system supports resuming interrupted operations:

  • Upload UUIDs are cached to resume validation polling
  • Extension IDs are saved to update existing addons
  • Network errors trigger automatic retries with exponential backoff
  • Detailed error messages help diagnose submission issues

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);
  }
}