> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/mercuryworkshop/scramjet/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom codecs

> Create custom URL encoding and decoding functions for Scramjet

Scramjet uses URL codecs to encode real URLs into proxy URLs and decode them back. You can customize this behavior by providing your own encoding and decoding functions.

## Default codec

By default, Scramjet uses simple URL encoding:

```javascript theme={null}
// Default encoder
function encode(url) {
  if (!url) return url;
  return encodeURIComponent(url);
}

// Default decoder
function decode(url) {
  if (!url) return url;
  return decodeURIComponent(url);
}
```

With the default codec and prefix `/scramjet/`, a URL like `https://example.com` becomes:

```
/scramjet/https%3A%2F%2Fexample.com
```

## Creating a custom codec

Custom codecs are defined when creating the `ScramjetController`:

```javascript theme={null}
const { ScramjetController } = $scramjetLoadController();

const scramjet = new ScramjetController({
  prefix: '/scramjet/',
  codec: {
    encode: (url) => {
      // Your custom encoding logic
      return btoa(url);
    },
    decode: (url) => {
      // Your custom decoding logic
      return atob(url);
    },
  },
});

await scramjet.init();
```

<Note>
  The encoder and decoder functions must be inverse operations - what you encode must be decodable back to the original URL.
</Note>

## Common codec implementations

### Base64 encoding

Base64 encoding makes URLs more obfuscated:

<Tabs>
  <Tab title="Basic">
    ```javascript theme={null}
    const scramjet = new ScramjetController({
      codec: {
        encode: (url) => {
          if (!url) return url;
          return btoa(url);
        },
        decode: (url) => {
          if (!url) return url;
          return atob(url);
        },
      },
    });
    ```
  </Tab>

  <Tab title="URL-safe">
    ```javascript theme={null}
    const scramjet = new ScramjetController({
      codec: {
        encode: (url) => {
          if (!url) return url;
          // Convert to URL-safe base64
          return btoa(url)
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
        },
        decode: (url) => {
          if (!url) return url;
          // Convert back from URL-safe base64
          let base64 = url
            .replace(/-/g, '+')
            .replace(/_/g, '/');
          // Add padding
          while (base64.length % 4) {
            base64 += '=';
          }
          return atob(base64);
        },
      },
    });
    ```
  </Tab>
</Tabs>

### XOR cipher encoding

Add simple obfuscation with an XOR cipher:

```javascript theme={null}
function xorCipher(str, key) {
  let result = '';
  for (let i = 0; i < str.length; i++) {
    result += String.fromCharCode(
      str.charCodeAt(i) ^ key.charCodeAt(i % key.length)
    );
  }
  return result;
}

const scramjet = new ScramjetController({
  codec: {
    encode: (url) => {
      if (!url) return url;
      const key = 'mySecretKey';
      const encrypted = xorCipher(url, key);
      return btoa(encrypted)
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    },
    decode: (url) => {
      if (!url) return url;
      const key = 'mySecretKey';
      // Add padding
      let base64 = url
        .replace(/-/g, '+')
        .replace(/_/g, '/');
      while (base64.length % 4) {
        base64 += '=';
      }
      const encrypted = atob(base64);
      return xorCipher(encrypted, key);
    },
  },
});
```

<Warning>
  XOR cipher provides minimal security and should not be relied upon for actual encryption. Use it only for obfuscation.
</Warning>

### Plain encoding

For maximum compatibility and debugging, use plain text URLs:

```javascript theme={null}
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => url,
    decode: (url) => url,
  },
});
```

<Note>
  Plain encoding requires special web server configuration to handle URLs containing `://` and other special characters.
</Note>

### Custom delimiter encoding

Use a custom delimiter instead of URL encoding:

```javascript theme={null}
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => {
      if (!url) return url;
      // Replace :// with a custom delimiter
      return url.replace('://', '---');
    },
    decode: (url) => {
      if (!url) return url;
      // Restore ://
      return url.replace('---', '://');
    },
  },
});

// https://example.com becomes /scramjet/https---example.com
```

## Hash fragment handling

Scramjet automatically handles URL hash fragments separately:

```javascript theme={null}
// Input URL: https://example.com/page#section
// The codec receives: https://example.com/page
// The hash is encoded separately: #section
```

Your codec doesn't need to handle hash fragments - Scramjet manages them automatically:

```javascript theme={null}
const url = new URL('https://example.com/page#section');

// Scramjet encodes the hash separately
const encodedHash = codecEncode(url.hash.slice(1));
const realHash = encodedHash ? '#' + encodedHash : '';

// The URL is encoded without the hash
url.hash = '';
const encodedUrl = config.prefix + codecEncode(url.href) + realHash;
```

## Using the controller's encode/decode methods

The `ScramjetController` provides helper methods for encoding and decoding URLs:

```javascript theme={null}
const { ScramjetController } = $scramjetLoadController();

const scramjet = new ScramjetController({
  prefix: '/scramjet/',
  codec: {
    encode: (url) => btoa(url),
    decode: (url) => atob(url),
  },
});

await scramjet.init();

// Encode a URL
const proxiedUrl = scramjet.encodeUrl('https://example.com');
console.log(proxiedUrl); // /scramjet/aHR0cHM6Ly9leGFtcGxlLmNvbQ

// Decode a URL
const realUrl = scramjet.decodeUrl(proxiedUrl);
console.log(realUrl); // https://example.com
```

<Tip>
  Use these methods instead of calling your codec functions directly to ensure proper prefix handling.
</Tip>

## Protocol handling

Scramjet only proxies HTTP and HTTPS URLs. Other protocols are passed through unchanged:

```javascript theme={null}
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => btoa(url),
    decode: (url) => atob(url),
  },
});

await scramjet.init();

// HTTP/HTTPS URLs are encoded
scramjet.encodeUrl('https://example.com');
// -> /scramjet/aHR0cHM6Ly9leGFtcGxlLmNvbQ

// Other protocols pass through
scramjet.encodeUrl('mailto:test@example.com');
// -> mailto:test@example.com

scramjet.encodeUrl('javascript:alert(1)');
// -> javascript:alert(1)
```

## Testing your codec

Here's a test suite for validating your custom codec:

```javascript theme={null}
function testCodec(encoder, decoder) {
  const testUrls = [
    'https://example.com',
    'https://example.com/path?query=value',
    'https://example.com:8080/path',
    'http://subdomain.example.com/path',
    'https://example.com/path?q=hello world&lang=en',
  ];

  console.log('Testing codec...');

  for (const url of testUrls) {
    const encoded = encoder(url);
    const decoded = decoder(encoded);

    if (url === decoded) {
      console.log('✓', url);
    } else {
      console.error('✗', url);
      console.error('  Expected:', url);
      console.error('  Got:', decoded);
    }
  }
}

// Test your codec
testCodec(
  (url) => btoa(url),
  (url) => atob(url)
);
```

## Performance considerations

<AccordionGroup>
  <Accordion title="Encoding speed">
    The codec functions are called frequently during page loads. Keep them fast:

    ```javascript theme={null}
    // Fast - simple operations
    encode: (url) => btoa(url)

    // Slower - complex operations
    encode: (url) => {
      // Multiple regex operations
      return url
        .replace(/:/g, '%3A')
        .replace(/\//g, '%2F')
        .replace(/\?/g, '%3F')
        // ... many more replacements
    }
    ```
  </Accordion>

  <Accordion title="URL length">
    Some codecs increase URL length significantly:

    ```javascript theme={null}
    const url = 'https://example.com';

    // URL encoding (default): 25 chars
    encodeURIComponent(url); // https%3A%2F%2Fexample.com

    // Base64: 28 chars
    btoa(url); // aHR0cHM6Ly9leGFtcGxlLmNvbQ==

    // XOR + Base64: 28+ chars
    ```

    Long URLs may cause issues with some servers or browsers.
  </Accordion>

  <Accordion title="Error handling">
    Always handle edge cases:

    ```javascript theme={null}
    const scramjet = new ScramjetController({
      codec: {
        encode: (url) => {
          if (!url) return url;
          try {
            return btoa(url);
          } catch (error) {
            console.error('Encoding failed:', error);
            // Fallback to default encoding
            return encodeURIComponent(url);
          }
        },
        decode: (url) => {
          if (!url) return url;
          try {
            return atob(url);
          } catch (error) {
            console.error('Decoding failed:', error);
            // Fallback to default decoding
            return decodeURIComponent(url);
          }
        },
      },
    });
    ```
  </Accordion>
</AccordionGroup>

## Related guides

<CardGroup cols={2}>
  <Card title="Basic setup" icon="rocket" href="/guides/basic-setup">
    Learn how to configure Scramjet
  </Card>

  <Card title="Configuration flags" icon="flag" href="/guides/configuration-flags">
    Explore other configuration options
  </Card>
</CardGroup>
