Skip to content

photon-hq/uri

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@photon-ai/uri

Generate URI links for iMessage, SMS, FaceTime, WhatsApp, and Telegram. Zero dependencies, fully typed.

npm TypeScript dependencies license Discord

A tiny, zero-dependency TypeScript library for generating correct, encoded URI strings that open native messaging apps with pre-filled recipients and message bodies.

Features

Feature Description Method Example
iMessage link Build an imessage:// URI for phone or email createIMessageLink() imessage.ts
SMS link Build an RFC 5724 sms: URI createSmsLink() sms.ts
FaceTime link Build facetime: / facetime-audio: URI createFaceTimeLink() facetime.ts
WhatsApp link Build wa.me universal or whatsapp:// scheme link createWhatsAppLink() whatsapp.ts
Telegram link Build t.me universal or tg:// scheme link createTelegramLink() telegram.ts
Unified dispatcher One function for all platforms via a discriminated union createLink() unified.ts
Parse URIs back Reverse operation: any supported URI to { platform, to, body, ... } parseLink() parse.ts

Quick Start

Installation

bun add @photon-ai/uri
npm install @photon-ai/uri

Basic usage

import { createIMessageLink, createLink, parseLink } from "@photon-ai/uri";

createIMessageLink({ to: "+14155551234", body: "hello" });
// imessage://+14155551234?body=hello

createLink({ platform: "sms", to: "+14155551234" });
// sms:+14155551234

parseLink("imessage://+14155551234?body=hi%20%F0%9F%91%8B");
// { platform: "imessage", to: "+14155551234", body: "hi πŸ‘‹" }

Core Concepts

Phone numbers

All phone inputs must be in E.164 format: a leading +, country code, and subscriber number with no spaces or formatting. The library normalizes common formatting (spaces, dashes, parentheses, dots) internally, but the + prefix is required.

import { assertE164 } from "@photon-ai/uri";

assertE164("+1 (415) 555-1234"); // "+14155551234"
assertE164("415-555-1234"); // throws InvalidPhoneNumberError

Recipient types per platform

Platform Phone Email Username Group Notes
iMessage yes yes β€” yes Phones and emails can be mixed in a group
SMS yes β€” β€” yes Phone only; RFC 5724 comma-separated list
FaceTime yes yes β€” β€” Email works for Apple IDs
WhatsApp yes β€” β€” β€” Strips + in URL path
Telegram yes β€” yes β€” Pre-filled body only for usernames

Platform quirks

  • FaceTime has no body support. Pass mode: "audio" for audio calls, and prompt: true to use the -prompt scheme variants that ask for confirmation before dialing.
  • WhatsApp strips the + from phone numbers in the URL; you still pass E.164 with + in options.
  • Telegram phone links (t.me/+...) do not support pre-filled message text; only username links support body.
  • SMS URIs follow RFC 5724 (sms:+phone?body=text), not iOS-specific variants.
  • Groups are supported for iMessage and SMS only. Pass to as an array; duplicates dedupe after normalization. A single-element array produces the same output as the string form.

Body encoding

Message bodies embedded in query strings use encodeBody() (and round-trip with decodeBody()). Reserved characters, newlines, and Unicode are percent-encoded per RFC 3986.

import { decodeBody, encodeBody } from "@photon-ai/uri";

encodeBody("hi πŸ‘‹"); // "hi%20%F0%9F%91%8B"
decodeBody(encodeBody("a\nb\nc")); // "a\nb\nc"

iMessage

Example: imessage.ts

Builds imessage:// URIs. Supports both phone numbers (E.164) and email addresses as recipients, including a mixed group of recipients.

import { createIMessageLink } from "@photon-ai/uri";

createIMessageLink({ to: "+14155551234" });
// imessage://+14155551234

createIMessageLink({ to: "+14155551234", body: "hi πŸ‘‹" });
// imessage://+14155551234?body=hi%20%F0%9F%91%8B

createIMessageLink({ to: "user@example.com", body: "hello" });
// imessage://user@example.com?body=hello

// Group β€” phones and emails can be mixed
createIMessageLink({
  to: ["+14155551234", "+14155556789", "user@example.com"],
  body: "standup at 3",
});
// imessage://+14155551234,+14155556789,user@example.com?body=standup%20at%203

Options

Field Type Required Description
to string | string[] yes One recipient, or a list. Each element is a phone (E.164) or email. Duplicates dedupe after normalization.
body string no Pre-filled message text

SMS

Example: sms.ts

Builds RFC 5724 sms: URIs. Recipients must be E.164 phone numbers. Multiple recipients are supported per RFC 5724 via a comma-separated list.

import { createSmsLink } from "@photon-ai/uri";

createSmsLink({ to: "+14155551234" });
// sms:+14155551234

createSmsLink({ to: "+14155551234", body: "Hello" });
// sms:+14155551234?body=Hello

// Group β€” RFC 5724 comma-separated list
createSmsLink({ to: ["+14155551234", "+14155556789"], body: "yo" });
// sms:+14155551234,+14155556789?body=yo

Options

Field Type Required Description
to string | string[] yes One E.164 phone, or a list. Duplicates dedupe after normalization; first-occurrence order preserved.
body string no Pre-filled message text

FaceTime

Example: facetime.ts

Builds facetime: (video) or facetime-audio: (audio) URIs. There is no message body; recipients are phone (E.164) or email (Apple ID).

import { createFaceTimeLink } from "@photon-ai/uri";

createFaceTimeLink({ to: "+14155551234" });
// facetime:+14155551234

createFaceTimeLink({ to: "+14155551234", mode: "audio" });
// facetime-audio:+14155551234

createFaceTimeLink({ to: "user@icloud.com", mode: "audio" });
// facetime-audio:user@icloud.com

// Prompt variants β€” iOS asks the user to confirm before dialing
createFaceTimeLink({ to: "+14155551234", prompt: true });
// facetime-prompt:+14155551234

createFaceTimeLink({ to: "+14155551234", mode: "audio", prompt: true });
// facetime-audio-prompt:+14155551234

Options

Field Type Required Description
to string yes E.164 phone or email
mode "video" | "audio" no Defaults to "video"
prompt boolean no Emit the -prompt scheme variant that shows a confirmation dialog before dialing. Recommended for public-web-page links. Defaults to false.

WhatsApp

Example: whatsapp.ts

Builds https://wa.me/... (universal) or whatsapp://send?... (scheme) links. Phone numbers are normalized to E.164 in options; the + is omitted in the generated path or phone query value.

import { createWhatsAppLink } from "@photon-ai/uri";

createWhatsAppLink({ to: "+14155551234", body: "Hello" });
// https://wa.me/14155551234?text=Hello

createWhatsAppLink({ to: "+14155551234", variant: "scheme", body: "hi" });
// whatsapp://send?phone=14155551234&text=hi

Options

Field Type Required Description
to string yes E.164 phone
body string no Pre-filled message text
variant "universal" | "scheme" no Defaults to "universal"

Telegram

Example: telegram.ts

Builds https://t.me/... (universal) or tg://resolve?... (scheme) links. Recipients are either E.164 phones (+... or digits) or valid usernames (@ prefix is stripped). Pre-filled body is supported for usernames only; phone links throw UnsupportedFeatureError if body is non-empty.

import { createTelegramLink } from "@photon-ai/uri";

createTelegramLink({ to: "durov", body: "hi πŸ‘‹" });
// https://t.me/durov?text=hi%20%F0%9F%91%8B

createTelegramLink({ to: "+14155551234" });
// https://t.me/+14155551234

createTelegramLink({ to: "durov", variant: "scheme" });
// tg://resolve?domain=durov

Options

Field Type Required Description
to string yes E.164 phone, digits, or username
body string no Pre-filled text (username only)
variant "universal" | "scheme" no Defaults to "universal"

Unified dispatcher

Example: unified.ts

createLink() dispatches to the correct builder from a single options object. The platform field discriminates the union so each platform only accepts its own options (for example, mode is only valid for FaceTime).

import { createLink } from "@photon-ai/uri";

createLink({ platform: "imessage", to: "+14155551234", body: "hi" });
// imessage://+14155551234?body=hi

createLink({ platform: "facetime", to: "+14155551234", mode: "audio" });
// facetime-audio:+14155551234

Parsing URIs

Example: parse.ts

parseLink() is the inverse of the builders for supported schemes: imessage://, sms:, facetime: / facetime-audio: / facetime-prompt: / facetime-audio-prompt:, https://wa.me/, https://t.me/, whatsapp://send, and tg://resolve. It returns a ParsedLink discriminated union with platform, to, optional body, and platform-specific fields (mode and prompt for FaceTime, variant for WhatsApp and Telegram).

For iMessage and SMS, to is typed as string | string[]: a single-recipient URI parses to a plain string (unchanged), and a multi-recipient URI parses to an array in original order.

import { parseLink } from "@photon-ai/uri";

parseLink("sms:+14155551234?body=hello");
// { platform: "sms", to: "+14155551234", body: "hello" }

parseLink("sms:+14155551234,+14155556789?body=yo");
// { platform: "sms", to: ["+14155551234", "+14155556789"], body: "yo" }

parseLink("facetime-audio:user@icloud.com");
// { platform: "facetime", to: "user@icloud.com", mode: "audio", prompt: false }

parseLink("facetime-prompt:+14155551234");
// { platform: "facetime", to: "+14155551234", mode: "video", prompt: true }

Round-trips are stable for values produced by the builders: build with a given option object, parse, then build again with the parsed fields and you get the same string where applicable.


Error handling

Errors extend MessageUriError and are safe to catch with instanceof:

Class When
InvalidPhoneNumberError Phone input is not valid E.164 after normalization
InvalidRecipientError Email or Telegram username validation failed
UnsupportedFeatureError Valid recipient but feature not available (e.g. Telegram phone + body)
UnrecognizedLinkError parseLink() input is not a supported URI shape
import {
  InvalidPhoneNumberError,
  parseLink,
  UnrecognizedLinkError,
} from "@photon-ai/uri";

try {
  parseLink("https://example.com/");
} catch (err) {
  if (err instanceof UnrecognizedLinkError) {
    // handle unknown link
  }
}

Examples

Run any example with Bun:

bun run examples/<filename>.ts

Per-platform

File Description
imessage.ts Build imessage:// URIs
sms.ts Build RFC 5724 sms: URIs
facetime.ts Build facetime: / facetime-audio: URIs
whatsapp.ts Build wa.me / whatsapp:// URIs
telegram.ts Build t.me / tg:// URIs

Advanced

File Description
unified.ts Unified createLink dispatcher
parse.ts Parse URIs back with parseLink

License

MIT


Author

Photon

About

Deep link builder for iMessage, SMS, FaceTime, WhatsApp, and Telegram. TypeScript, zero deps.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors