Modules overview
What modules are, where they work, how to add and manage them, and what to know before you enable one.
A module is a file from a third-party author that changes requests and responses of selected sites, or blocks domains, inside the VPN tunnel. Modules exist only on iPhone. Letakof ships no ready-made modules and no catalog: you add one yourself, and a new module works without an app update.
Modules are an advanced feature. A module’s script sees the headers, cookies and bodies of the requests of the hosts it handles. Add only modules whose code you have read. A link and a SHA-256 do not prove who wrote a module.
The format is Letakof’s own. It is not compatible with Shadowrocket, Surge or Loon modules.
Where
Settings → Modules (the count of enabled modules is shown on the right). The screen has these groups:
| Group | What it is |
|---|---|
| Installed | Your modules: icon, name, status and a switch. Tap one to open its card. Swipe left to delete a module that is off. |
| Drafts | Modules saved from the editor. |
| Add | From a link, From file and Create manually. |
| Certificate | Module certificate; see Certificate and MITM. |
| Do not decrypt | Hosts you excluded, with Restore. |
Adding a module
Every way ends on the New module preview, and nothing runs until you confirm it.
| Way | Details |
|---|---|
Link letakof://module/add?url=<https address> |
Optional sha256=<64 hex> pins the content. Other or repeated parameters are rejected. |
| From a link | An https:// address and an optional SHA-256. A GitHub file page is replaced by the raw file. |
| From file | Any file up to 256 KiB. |
| Create manually | An editor with tabs Manifest, one per script, and Check. You can save a draft, install it or export the manifest. |
Old .module / .sgmodule |
Detected by a file that does not start with {. Only DOMAIN and DOMAIN-SUFFIX REJECT rules and the [MITM] host list are carried over. Everything else is listed as not carried over. |
Links must use https, no credentials, no local or private addresses; redirects are re-checked (up to 5); a web page instead of a file is refused. See Module structure for the file itself.
The preview shows the icon, name, version, the author as declared by the module, the source, what the module can do, the hosts that will be decrypted, the rules, the parameters and the full code of every script. For an update it also shows what changed, including new hosts and permissions. Buttons: Add and enable or Add without enabling. Updates are manual: Check for update on the card repeats the download and shows the difference.
Example: the starter module
Tap Add → Create manually and the editor opens with a ready text. It is neutral: no real services, only a placeholder host. This is the manifest, on the Manifest tab:
{
"schemaVersion": 1,
"id": "my.module",
"name": "My module",
"version": "1.0.0",
"author": "",
"parameters": [
{ "id": "value", "title": "Value", "type": "string", "default": "one", "enum": ["one", "two"] }
],
"mitmHosts": ["api.example.org"],
"httpRules": [
{ "id": "main", "phase": "request", "host": "api.example.org", "path": { "prefix": "/" },
"action": { "type": "script", "script": "main" } }
],
"scripts": [ { "id": "main" } ]
}
And this is the code on the tab of the script main (the app shows its comment in Russian):
// transform(event, context) → patch | null. null leaves the exchange unchanged.
function transform(event, context) {
return null;
}
| Part | What it means |
|---|---|
schemaVersion |
The format version. Always 1. |
id |
The module’s unique name and the name of its storage. Change it for your own module. |
name, version, author |
What the list and the card show. author is optional and here empty. |
parameters |
One setting, value: a string, default one, with the choices one and two. The card shows it as a choice, and the script reads it as context.parameters.value. |
mitmHosts |
The hosts whose HTTPS traffic is decrypted. api.example.org is a placeholder: put the host you need. This is the part that requires the certificate. |
httpRules |
One rule named main. phase: request means before the request is sent. host must be one of mitmHosts. path.prefix: "/" matches every path. action runs the script with the id main. |
scripts |
Only names the script. Its code is on the script’s own tab in the editor. |
transform |
Called for every exchange the rule matches. It returns null, so the starter changes nothing: it becomes useful when you return a patch. |
Replace the host and the path, write the patch in transform, and use the Check tab before Install…. All fields and the patch format are in Module structure.
Managing
- A switch in the list or Enabled on the card. A module works only while the VPN is connected, also in Direct mode. Changes apply at once if the VPN is on.
- Several modules apply in the order they were installed; the interface cannot reorder them. For domain rules the first module with a match decides. For HTTP rules the first matching rule of a phase wins and the rest are skipped. The engine accepts up to 32 enabled modules.
- A module’s card shows its status: Off, Waiting for the VPN, Being applied, Certificate required, Certificate trust required, Applied, Error, TLS incompatible, Resource limit, Hostname unavailable; plus Decrypted hosts, counters and Host diagnostics.
- Deleting removes the module, its parameters and its data.
Limits
| What | Limit |
|---|---|
| Manifest, script, headers | 256 KiB, 256 KiB, 64 KiB |
| HTTP rules, hosts | 256 rules; 64 mitmHosts and 64 excludedHosts |
| Script run | 100 ms, 8 MiB heap, a fresh runtime for each call |
| Script storage | 64 KiB per module, 1 MiB in total |
| Bodies | 1 MiB over the network, 2 MiB after decoding |
Anything that goes wrong in a script (an exception, time, memory, a bad patch) means the original exchange reaches the app unchanged. Only TCP on ports 443 and 80 is handled; QUIC is rejected locally so the app falls back to TCP; WebSocket, video, audio, event streams and gRPC pass through unchanged.
Found a mistake? Open an issue on GitHub.