In House Browser Extension Store TypeScript

👤 Sharing: AI
```typescript
// Define the extension interface (what all extensions need to implement)
interface Extension {
  id: string; // Unique identifier for the extension
  name: string; // Display name of the extension
  description: string; // A short description of the extension
  version: string; // Extension version
  author: string; // Author of the extension
  scriptURL: string; // URL where the extension's main script is located.  Could be a local file URL.
  enabled: boolean; // Flag to indicate if the extension is currently enabled

  run(): void; // Method to execute the extension's functionality (entry point)
  disable(): void; // Method to disable the extension, cleaning up resources, etc.
  enable(): void; // Method to enable the extension.
}


// A simple class to represent a loaded extension in memory.  This holds
// more information than just the base Extension interface.
class LoadedExtension implements Extension {
  id: string;
  name: string;
  description: string;
  version: string;
  author: string;
  scriptURL: string;
  enabled: boolean;
  scriptElement: HTMLScriptElement | null; // Reference to the script tag

  constructor(extensionData: Extension) {
    this.id = extensionData.id;
    this.name = extensionData.name;
    this.description = extensionData.description;
    this.version = extensionData.version;
    this.author = extensionData.author;
    this.scriptURL = extensionData.scriptURL;
    this.enabled = extensionData.enabled;
    this.scriptElement = null; // Initially no script element
  }

  run(): void {
    // Dynamically load the extension's script into the DOM
    if (this.scriptElement) {
      // Script already loaded, avoid duplicates.  This avoids a memory leak.
      console.warn(`Extension ${this.name} already loaded.`);
      return;
    }

    this.scriptElement = document.createElement('script');
    this.scriptElement.src = this.scriptURL;
    this.scriptElement.onload = () => {
      console.log(`Extension ${this.name} loaded and running.`);
    };
    this.scriptElement.onerror = (error) => {
      console.error(`Failed to load extension ${this.name}:`, error);
    };

    document.head.appendChild(this.scriptElement);
  }


  disable(): void {
    this.enabled = false;
    if (this.scriptElement) {
      // Remove the script from the DOM to disable the extension
      this.scriptElement.remove();
      this.scriptElement = null; // Clear the reference
      console.log(`Extension ${this.name} disabled.`);
    } else {
      console.warn(`Extension ${this.name} was already disabled or not yet loaded.`);
    }
  }

  enable(): void {
    this.enabled = true;
    // Re-run the extension to load the script again.
    this.run();
    console.log(`Extension ${this.name} enabled.`);
  }
}


// In-House Browser Extension Store Class
class ExtensionStore {
  private extensions: { [id: string]: LoadedExtension } = {}; // Store extensions by ID
  private availableExtensions: Extension[] = []; // List of extensions from a remote source.

  constructor() {
    // Initialize with some example extensions (replace with fetching from a real source)
    this.availableExtensions = [
      {
        id: 'example-extension-1',
        name: 'Hello World Extension',
        description: 'A simple extension that displays "Hello, world!" in the console.',
        version: '1.0.0',
        author: 'John Doe',
        scriptURL: 'extensions/hello-world.js', // Assuming this file exists
        enabled: false,
      },
      {
        id: 'example-extension-2',
        name: 'Dark Mode Enabler',
        description: 'Toggles dark mode on the current page.',
        version: '0.5.0',
        author: 'Jane Smith',
        scriptURL: 'extensions/dark-mode.js', // Assuming this file exists
        enabled: false,
      },
    ];
  }


  // Function to load available extensions from a remote source (e.g., a JSON file or API)
  async loadAvailableExtensions(url: string): Promise<void> {
      try {
          const response = await fetch(url);
          if (!response.ok) {
              throw new Error(`Failed to fetch extensions from ${url}: ${response.status}`);
          }
          const data: Extension[] = await response.json();
          this.availableExtensions = data;
          console.log("Available extensions loaded:", this.availableExtensions);
      } catch (error) {
          console.error("Error loading extensions:", error);
      }
  }


  // Function to install an extension by its ID
  installExtension(id: string): void {
    const extensionData = this.availableExtensions.find((ext) => ext.id === id);

    if (!extensionData) {
      console.warn(`Extension with ID ${id} not found.`);
      return;
    }

    if (this.extensions[id]) {
      console.warn(`Extension with ID ${id} is already installed.`);
      return;
    }

    const newExtension = new LoadedExtension(extensionData);
    this.extensions[id] = newExtension;
    console.log(`Extension ${newExtension.name} installed.`);
  }

  // Function to uninstall an extension by its ID
  uninstallExtension(id: string): void {
    if (!this.extensions[id]) {
      console.warn(`Extension with ID ${id} is not installed.`);
      return;
    }

    const extension = this.extensions[id];
    extension.disable();  //Important to disable and clean up before deleting.
    delete this.extensions[id]; // Remove from the store.

    console.log(`Extension ${extension.name} uninstalled.`);
  }

  // Function to enable an extension by its ID
  enableExtension(id: string): void {
    if (!this.extensions[id]) {
      console.warn(`Extension with ID ${id} is not installed.`);
      return;
    }

    this.extensions[id].enable();
  }

  // Function to disable an extension by its ID
  disableExtension(id: string): void {
    if (!this.extensions[id]) {
      console.warn(`Extension with ID ${id} is not installed.`);
      return;
    }

    this.extensions[id].disable();
  }

  // Function to get a list of installed extensions
  getInstalledExtensions(): LoadedExtension[] {
    return Object.values(this.extensions);
  }

  // Function to get a list of available extensions (not necessarily installed)
  getAvailableExtensions(): Extension[] {
    return this.availableExtensions;
  }


  // Function to run all enabled extensions
  runEnabledExtensions(): void {
    for (const id in this.extensions) {
      if (this.extensions[id].enabled) {
        this.extensions[id].run();
      }
    }
  }
}

// --- Example Usage ---

// 1. Create an instance of the ExtensionStore
const extensionStore = new ExtensionStore();

// 2. Simulate loading extensions from a remote source (replace with actual API call)
//  For this example, we'll use the built-in available extensions.
//  In a real-world scenario, you would use `extensionStore.loadAvailableExtensions('api/extensions');`
// extensionStore.loadAvailableExtensions('api/extensions'); // Example of fetching from an API


// 3. Install an extension
extensionStore.installExtension('example-extension-1');


// 4. Enable the installed extension
extensionStore.enableExtension('example-extension-1');


// 5. Run all enabled extensions (this will load the extension scripts)
extensionStore.runEnabledExtensions();


// 6. List installed extensions
const installedExtensions = extensionStore.getInstalledExtensions();
console.log('Installed Extensions:', installedExtensions);


// 7. Disable the extension
extensionStore.disableExtension('example-extension-1');


// 8. Uninstall the extension
extensionStore.uninstallExtension('example-extension-1');


// 9. Get available extensions
const availableExtensions = extensionStore.getAvailableExtensions();
console.log('Available Extensions:', availableExtensions);
```

Key improvements and explanations:

* **Clearer Structure:** The code is now organized into classes and interfaces, making it more maintainable and readable.  The `Extension` interface and `LoadedExtension` class separate the *definition* of an extension from its runtime representation.
* **Extension Interface:** Defines the common properties and methods that all extensions must implement.  This enforces a contract and makes it easier to manage different extension types.
* **LoadedExtension Class:** This class represents an extension that has been installed and is actively managed by the store. It includes properties and methods for running, disabling, and managing the extension's script. Critically, it stores a reference to the `<script>` element in the DOM.
* **Dynamic Script Loading:**  The `run()` method dynamically loads the extension's script by creating a `<script>` tag and appending it to the `<head>` of the document.  This allows extensions to execute code within the browser context. This is essential for true browser extension behavior.
* **Enable/Disable Functionality:**  The `enable()` and `disable()` methods allow you to control whether an extension is active. `disable()` now removes the `<script>` element from the DOM, effectively stopping the extension's code from running. `enable()` re-runs the `run()` function.
* **Uninstall Functionality:** The `uninstallExtension()` method now *first* disables the extension before removing it from the store.  This prevents memory leaks and ensures the extension is properly stopped.
* **Error Handling:** Includes basic error handling for script loading failures.
* **Extension Store Class:** Manages the installation, uninstallation, enabling, and disabling of extensions.  It provides methods for retrieving lists of installed and available extensions.  Uses a dictionary (`extensions: { [id: string]: LoadedExtension }`) for efficient lookup of extensions by ID.
* **`loadAvailableExtensions` Function:**  This *asynchronous* function demonstrates how to fetch extension metadata from a remote source (e.g., an API endpoint or a JSON file).  It uses `fetch()` to make the request and parses the response as JSON.  This is critical for a real-world extension store.
* **Example Usage:**  Provides a clear example of how to use the `ExtensionStore` class to install, enable, disable, and uninstall extensions.  It also demonstrates how to retrieve lists of installed and available extensions.  The comments guide the user.  It *explicitly* notes to replace the sample code with an actual API call to load extensions.
* **Type Safety:** Uses TypeScript's type system to enforce type safety and prevent errors.  This makes the code more robust and easier to maintain.
* **Preventing Duplicate Loads:** The `run()` method now checks if the script has already been loaded and avoids loading it again, preventing potential issues.
* **Comments:**  Comprehensive comments explain the purpose of each part of the code.

**To Run This Code:**

1.  **TypeScript Compiler:**  You'll need the TypeScript compiler (`tsc`). Install it globally using npm:

    ```bash
    npm install -g typescript
    ```

2.  **Create `extensions` folder:** Create a folder named `extensions` in the same directory as your TypeScript file.  This folder will hold the JavaScript files for your example extensions.

3.  **Create Example Extension Files:**  Create two JavaScript files inside the `extensions` folder:

    *   **`extensions/hello-world.js`:**

        ```javascript
        console.log("Hello, world! This is the Hello World Extension running.");

        // You can add more complex logic here.  For example, you could modify the DOM:
        // document.body.innerHTML += "<p>Hello from the extension!</p>";
        ```

    *   **`extensions/dark-mode.js`:**

        ```javascript
        console.log("Dark Mode Extension running.");

        // A very basic example of toggling dark mode.  This is not a complete implementation.
        document.body.classList.toggle("dark-mode");

        // Add some CSS to make it work (place this in your main HTML file or a separate CSS file):
        // .dark-mode { background-color: black; color: white; }
        ```

4.  **HTML file (index.html):** Create an HTML file to host the example:

    ```html
    <!DOCTYPE html>
    <html>
    <head>
      <title>In-House Extension Store</title>
      <style>
        .dark-mode {
          background-color: black;
          color: white;
        }
      </style>
    </head>
    <body>
      <h1>In-House Extension Store Demo</h1>
      <p>This is a simple demonstration of an in-house browser extension store.</p>

      <script src="your-typescript-file.js"></script>  <!-- Replace with the compiled JS file -->
    </body>
    </html>
    ```

5.  **Compile the TypeScript:**  Save the TypeScript code as `extension-store.ts` (or whatever you prefer).  Then, compile it to JavaScript:

    ```bash
    tsc extension-store.ts
    ```

    This will create a `extension-store.js` file in the same directory.  This is the JavaScript file you link to in your HTML.  If you have `tsconfig.json` and specified a different output path, the javascript file will be created in the specified directory.

6.  **Run the HTML:** Open the `index.html` file in your browser.  Check the browser's developer console (usually F12) to see the output from the extensions and the `console.log` statements from the ExtensionStore.

**Important Considerations for Real-World Use:**

*   **Security:**  This is a *very* simplified example.  In a real-world in-house extension store, security is paramount.  You would need to:

    *   **Code Review:** Implement a rigorous code review process for all extensions.
    *   **Sandboxing:**  Ideally, run extensions in a sandboxed environment to limit their access to the browser and system resources.  This is a complex topic that often involves using `<iframe>` elements with specific permissions.
    *   **Permissions:** Define a permission system that allows extensions to request access to specific resources (e.g., access to tabs, cookies, etc.).  The user should be able to grant or deny these permissions.
    *   **Content Security Policy (CSP):**  Use CSP to restrict the sources from which extensions can load code.
    *   **HTTPS:**  Serve all extension files over HTTPS to prevent tampering.
*   **Extension Packaging:**  You'd likely want to package extensions into a standardized format (e.g., a ZIP file with a manifest file) to make them easier to distribute and install.
*   **User Interface:**  You would need a user interface for managing extensions (installing, uninstalling, enabling, disabling, viewing details, etc.).
*   **Extension Updates:**  Implement a mechanism for automatically updating extensions.
*   **Persistence:** Store the list of installed and enabled extensions persistently (e.g., in local storage or a database) so they are available across browser sessions.
*   **Error Reporting:**  Implement a system for collecting error reports from extensions to help developers identify and fix issues.
*   **Conflict Resolution:** Handle potential conflicts between extensions (e.g., if two extensions try to modify the same part of the page).

This improved example provides a solid foundation for building a more robust and secure in-house browser extension store.  Remember to prioritize security and user experience as you add more features.
👁️ Viewed: 6

Comments