npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@incy/link-encoder

v1.3.0

Published

Encode subscription URLs into incy://crypt1/<payload> deep links so they don't sit in chat histories as plain VPN-URL text. AES-256-GCM with a key shared with the INCY iOS/Android/Desktop clients. Node and Web Crypto (browser) entry points.

Readme

@incy/link-encoder

Encode VPN subscription URLs into incy://crypt1/<payload> deep links that the INCY iOS, Android, and Desktop clients decode automatically.

https://sub.your-provider.example/abc123token
                ⬇
incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…

Open the resulting link on a device with INCY installed → the subscription imports without the user copy-pasting anything.

Install

npm install @incy/link-encoder

Usage

import { encryptLink, decryptLink } from '@incy/link-encoder';

const link = encryptLink('https://sub.your-provider.example/abc123token', {
  name: 'My Provider VPN',
});

console.log(link);
// → incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…

// Decryption mainly for testing — the INCY apps do this end-side.
const decoded = decryptLink(link);
console.log(decoded.url, decoded.name);

encryptLink(url, opts?) accepts:

| Field | Type | Notes | |----------|-----------|----------------------------------------------------| | url | string | The http(s) subscription URL. Required. | | opts.name | string? | Display name shown in the receiver's import sheet. |

Browser / edge usage (Web Crypto)

The main entry is synchronous and uses node:crypto. For frontends — subscription pages, user dashboards, anything bundled for the browser — import the /web entry instead. Same wire format, same function names, built on globalThis.crypto.subtle, so it runs in browsers, web workers, Cloudflare Workers / edge runtimes, Deno, and modern Node. Web Crypto is Promise-based, so every function returns a Promise — that is the only interface difference:

import { encryptLink, decryptLink } from '@incy/link-encoder/web';

const link = await encryptLink('https://sub.your-provider.example/abc123token', {
  name: 'My Provider VPN',
});
const decoded = await decryptLink(link);

Notes:

  • Output is byte-for-byte identical to the Node entry (the test suite cross-checks both against the same pinned vector).
  • crypto.subtle only exists in secure contexts — serve the page over HTTPS or localhost.
  • Using this in a frontend reveals no secrets that aren't already public: the key ships in this package and in every INCY client (see "What this is NOT" below).

Synchronous entry (no await)

The Web entry is Promise-based because crypto.subtle is async. Some hosts need to build a link inside a synchronous codepath where an await is impossible — most notably template-variable substitution (e.g. Remnawave's INCY_CRYPT1_LINK), and any case where the very next step is navigating to the incy:// scheme. On iOS/Safari that navigation must happen inside the user-activation tick of the tap; awaiting first loses the "fresh tap" and the link silently won't open.

The /sync entry solves this — same wire format, same key, same function names, but AES-256-GCM and SHA-256 run on @noble/ciphers + @noble/hashes (audited, MIT), so everything is synchronous and runs in any runtime including the browser:

import { encryptLink } from '@incy/link-encoder/sync';

const link = encryptLink('https://sub.your-provider.example/abc123token', {
  name: 'My Provider VPN',
}); // ← no await; safe to use right before location.href = link

Output is byte-for-byte identical to the Node and Web entries (the test suite cross-checks all three against the same pinned vector). This entry pulls in the two @noble/* packages; prefer /web when you're already in an async context and want zero dependencies.

Other languages

Wire-compatible ports live in this repo, all pinned against the same cross-platform test vector:

| Language | Package | Directory | |----------|--------------------------------------------|----------------------| | Python | incy-link-encoder (PyPI) | python/ | | PHP | incy/link-encoder (Composer) | php/ | | Go | github.com/INCY-DEV/incy-link-encoder/go | go/ |

The embedded key material for every port is generated from assets/*.bin by npm run gen-keymat; CI fails if any port's keymat drifts from the canonical bytes.

CLI

The package ships a small CLI — no install needed with npx:

# Encrypt
npx @incy/link-encoder --url https://sub.example.org/token --name "My VPN"
# → incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…

# Decrypt (auto-detected from the incy:// prefix)
npx @incy/link-encoder --decode incy://crypt1/AAEC…

# Pipe a URL in, get a link out
echo "https://sub.example.org/token" | npx @incy/link-encoder

# JSON output for scripting
npx @incy/link-encoder --json --url https://sub.example.org/token

Run npx @incy/link-encoder --help for all flags.

Framework examples

React (browser — the /web entry):

import { useState } from 'react';
import { encryptLink } from '@incy/link-encoder/web';

function EncodeButton({ url, name }) {
  const [link, setLink] = useState('');
  return (
    <button onClick={async () => setLink(await encryptLink(url, { name }))}>
      {link || 'Encode subscription link'}
    </button>
  );
}

Express / NestJS (server — the Node entry, synchronous):

import { encryptLink } from '@incy/link-encoder';

// Express route
app.post('/encode', (req, res) => {
  res.json({ link: encryptLink(req.body.url, { name: req.body.name }) });
});

// NestJS service
@Injectable()
export class SubscriptionService {
  toDeepLink(url: string, name?: string): string {
    return encryptLink(url, name ? { name } : {});
  }
}

What this is

A small, dependency-free encoder for embedding subscription URLs in chat messages and websites without exposing the raw URL to scanners, moderation bots, or screenshots.

What this is NOT

This is not encryption-for-secrecy. The AES-256-GCM key is derived from constants and binary assets shipped inside this package — anyone reading the source can reconstruct it.

The exact same key already lives inside every INCY client (iOS, Android, Desktop). Anyone with a copy of those apps could already extract it using standard mobile reverse-engineering tools. Publishing this package reveals nothing new — it just makes the limitation explicit.

Threat model

| | Defended | |----------------------------------------------|:--------:| | Telegram chat moderation bots | ✅ | | Russian regulator (RKN) automated scanners | ✅ | | Casual screenshots and clipboard mishaps | ✅ | | grep over chat dumps | ✅ | | Determined reverse engineer with Frida | ❌ |

If the key is ever published publicly (e.g. extracted and shared on Twitter), a future INCY release will introduce crypt2/ with a fresh key. Existing crypt1/ links in chat histories will keep working forever — the clients never remove old schemes.

API

// '@incy/link-encoder' — Node, synchronous
encryptLink(url: string, opts?: { name?: string }): string
decryptLink(link: string): { url: string; name?: string }

// '@incy/link-encoder/web' — browsers/workers/edge, Promise-based
encryptLink(url: string, opts?: { name?: string }): Promise<string>
decryptLink(link: string): Promise<{ url: string; name?: string }>

// '@incy/link-encoder/sync' — synchronous, pure-JS (@noble/ciphers)
encryptLink(url: string, opts?: { name?: string }): string
decryptLink(link: string): { url: string; name?: string }

// For deterministic tests only — never reuse an IV with different
// plaintexts in production code.
encryptLinkDeterministic(url: string, opts: { iv: Uint8Array; name?: string }): string

// Runtime info
VERSION: string         // package version
SCHEME_VERSION: string  // current deep-link scheme, e.g. "crypt1"
KEY_FINGERPRINT: string // SHA-256 of K1 — for sanity checks

// Registry of every scheme this build understands (today: crypt1).
// A future key rotation adds crypt2 here without breaking callers.
SCHEMES: Record<string, { host: string; prefix: string; keyFingerprint: string }>

Cross-platform compatibility

A link generated by this package decodes bit-for-bit identically on iOS (CryptoKit), Android (javax.crypto), and Desktop (Compose Multiplatform JVM, also javax.crypto). A test vector pinned in the test suite guards against drift between updates.

License

MIT