Albatross SDK
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
@@ -0,0 +1,116 @@
|
||||
# @usealbatross/sdk
|
||||
|
||||
Official SDK for [Albatross](https://albatrossprotocol.com), the privacy protocol on Solana.
|
||||
|
||||
Move SOL privately with the bridge, or send SOL to anyone with a single unlinkable
|
||||
Ghost Link. No wallet connection, no signup, noncustodial.
|
||||
|
||||
```bash
|
||||
npm install @usealbatross/sdk
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { Albatross } from '@usealbatross/sdk'
|
||||
|
||||
const albatross = new Albatross()
|
||||
|
||||
// 1. Create a private bridge
|
||||
const bridge = await albatross.createBridge({
|
||||
amount: 1, // SOL the recipient receives (0.1, 0.5, 1 or 5)
|
||||
destination: 'YourCleanWallet…',
|
||||
})
|
||||
|
||||
console.log(`Send ${bridge.depositSol} SOL to ${bridge.depositAddress}`)
|
||||
|
||||
// 2. Wait until the payout lands
|
||||
const result = await albatross.waitForBridge(bridge.id, {
|
||||
onUpdate: (s) => console.log(s.status),
|
||||
})
|
||||
|
||||
console.log('Done:', result.payoutSignature)
|
||||
```
|
||||
|
||||
Even shorter, with the one shot helper:
|
||||
|
||||
```ts
|
||||
const { bridge, wait } = await albatross.bridge({ amount: 1, destination })
|
||||
console.log(`Send ${bridge.depositSol} SOL to ${bridge.depositAddress}`)
|
||||
const done = await wait()
|
||||
```
|
||||
|
||||
## Ghost Links
|
||||
|
||||
Send SOL to anyone with just a link. No address, no wallet connect, no onchain trail
|
||||
between sender and receiver. The claim secret lives only in the URL fragment and is
|
||||
never sent to or stored by the server.
|
||||
|
||||
```ts
|
||||
import { Albatross } from '@usealbatross/sdk'
|
||||
|
||||
const albatross = new Albatross()
|
||||
|
||||
// 1. Create a Ghost Link (secret + claim URL are generated locally)
|
||||
const { link, claimUrl } = await albatross.createGhostLink({
|
||||
amount: 0.5, // SOL the receiver claims (0.1, 0.5, 1 or 5)
|
||||
refundAddress: 'YourWallet…', // where funds return if never claimed
|
||||
note: 'gm 👻', // optional, shown to the receiver
|
||||
})
|
||||
|
||||
console.log(`Fund it: send ${link.depositSol} SOL to ${link.depositAddress}`)
|
||||
console.log(`Then share: ${claimUrl}`) // keep this safe — it can never be recovered
|
||||
|
||||
// 2. The receiver claims to any wallet
|
||||
await albatross.claimFromUrl(claimUrl, 'ReceiverWallet…')
|
||||
```
|
||||
|
||||
⚠️ The `secret` and `claimUrl` are never stored server side and cannot be recovered.
|
||||
Persist or share them the moment you create the link.
|
||||
|
||||
## How the bridge works
|
||||
|
||||
You send SOL to the fresh `depositAddress`. Albatross detects it and pays the same
|
||||
amount to your `destination` from an unrelated wallet. The two transfers are never
|
||||
linked onchain. The fee is 0.5%, added on top of the amount. Ghost Links work the
|
||||
same way, but the payout goes to whoever claims the link.
|
||||
|
||||
## API
|
||||
|
||||
### `new Albatross(options?)`
|
||||
|
||||
| Option | Type | Default |
|
||||
|---|---|---|
|
||||
| `baseUrl` | `string` | `https://backend.albatrossprotocol.com/api` |
|
||||
| `appUrl` | `string` | `https://albatrossprotocol.com` |
|
||||
| `fetch` | `typeof fetch` | global `fetch` |
|
||||
|
||||
### Bridge methods
|
||||
|
||||
- `createBridge({ amount, destination })` → `Bridge` — create a bridge, returns the deposit address.
|
||||
- `getBridge(id)` → `BridgeState` — fetch current status.
|
||||
- `waitForBridge(id, opts?)` → `BridgeState` — poll until terminal (`completed`, `refunded`, `failed`, `expired`).
|
||||
- `bridge({ amount, destination })` → `{ bridge, wait() }` — create and get a ready `wait()` helper.
|
||||
- `getAmounts()` → `{ amounts, feeBps }` — selectable amounts and fee.
|
||||
- `getStats()` → `{ bridges, volumeSol, fundingWallets, feeBps }` — network stats.
|
||||
|
||||
### Ghost Link methods
|
||||
|
||||
- `createGhostLink({ amount, refundAddress, note? })` → `{ link, secret, claimUrl }` — create a link locally hashing the secret.
|
||||
- `getGhostLink(id)` → `GhostLinkState` — fetch current status.
|
||||
- `claimGhostLink({ id, secret, destination })` → claim to a wallet.
|
||||
- `claimFromUrl(claimUrl, destination)` → parse a claim URL and claim it.
|
||||
- `waitForGhostLink(id, opts?)` → poll until terminal (`claimed`, `refunded`, `failed`, `expired`).
|
||||
- `getGhostAmounts()` → `{ amounts, feeBps, claimExpiryDays }`.
|
||||
- `buildClaimUrl(id, secret)` → build a shareable claim URL.
|
||||
- `generateClaimSecret()` (named export) → `{ secret, secretHash }` low level helper.
|
||||
|
||||
### Statuses
|
||||
|
||||
Bridge: `awaiting_deposit` → `paying_out` → `completed`, or `refunded` / `failed` / `expired`.
|
||||
|
||||
Ghost Link: `awaiting_deposit` → `funded` → `claiming` → `claimed`, or `refunded` / `failed` / `expired`.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Generated
+1473
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@usealbatross/sdk",
|
||||
"version": "0.2.0",
|
||||
"description": "Official SDK for Albatross, the privacy protocol on Solana. Private bridging and unlinkable Ghost Links in a few lines of code.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format esm,cjs --dts --clean --minify"
|
||||
},
|
||||
"keywords": [
|
||||
"albatross",
|
||||
"solana",
|
||||
"privacy",
|
||||
"bridge",
|
||||
"sdk",
|
||||
"crypto",
|
||||
"web3"
|
||||
],
|
||||
"author": "Albatross",
|
||||
"license": "MIT",
|
||||
"homepage": "https://albatrossprotocol.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/usealbatross/sdk"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.3.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
/**
|
||||
* @usealbatross/sdk
|
||||
* Official SDK for Albatross — the privacy protocol on Solana.
|
||||
*
|
||||
* Move SOL privately with the bridge, or send SOL to anyone with a single
|
||||
* unlinkable Ghost Link. No wallet connection, noncustodial.
|
||||
*/
|
||||
|
||||
export const DEFAULT_BASE_URL = 'https://backend.albatrossprotocol.com/api';
|
||||
export const DEFAULT_APP_URL = 'https://albatrossprotocol.com';
|
||||
|
||||
export type BridgeStatus =
|
||||
| 'awaiting_deposit'
|
||||
| 'deposit_detected'
|
||||
| 'paying_out'
|
||||
| 'completed'
|
||||
| 'refunding'
|
||||
| 'refunded'
|
||||
| 'failed'
|
||||
| 'expired';
|
||||
|
||||
/** Terminal states: the bridge will not change after reaching one of these. */
|
||||
export const TERMINAL_STATUSES: BridgeStatus[] = ['completed', 'refunded', 'failed', 'expired'];
|
||||
|
||||
export interface AlbatrossOptions {
|
||||
/** API base URL. Defaults to the public Albatross API. */
|
||||
baseUrl?: string;
|
||||
/** Web app URL used to build shareable Ghost Link URLs. Defaults to albatrossprotocol.com. */
|
||||
appUrl?: string;
|
||||
/** Custom fetch implementation (for environments without global fetch). */
|
||||
fetch?: typeof fetch;
|
||||
}
|
||||
|
||||
export interface Amounts {
|
||||
/** Selectable bridge amounts, in SOL. */
|
||||
amounts: number[];
|
||||
/** Bridge fee in basis points (50 = 0.5%). */
|
||||
feeBps: number;
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
bridges: number;
|
||||
volumeSol: number;
|
||||
fundingWallets: number;
|
||||
feeBps: number;
|
||||
}
|
||||
|
||||
export interface CreateBridgeParams {
|
||||
/** Amount to bridge (what the recipient receives). Must be one of the allowed amounts. */
|
||||
amount: number;
|
||||
/** Destination Solana address that receives the clean payout. */
|
||||
destination: string;
|
||||
}
|
||||
|
||||
export interface Bridge {
|
||||
id: string;
|
||||
/** Amount the recipient receives, in SOL. */
|
||||
amountSol: number;
|
||||
/** Amount the user must send (amount + fee), in SOL. */
|
||||
depositSol: number;
|
||||
/** Bridge fee, in SOL. */
|
||||
feeSol: number;
|
||||
/** Freshly generated address to send the deposit to. */
|
||||
depositAddress: string;
|
||||
destinationAddress: string;
|
||||
status: BridgeStatus;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export interface BridgeState extends Bridge {
|
||||
depositedSol: number;
|
||||
/** Payout transaction signature, once completed. */
|
||||
payoutSignature: string | null;
|
||||
/** Refund transaction signature, if refunded. */
|
||||
refundSignature: string | null;
|
||||
senderAddress: string | null;
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface WaitOptions {
|
||||
/** Poll interval in ms. Default 5000. */
|
||||
intervalMs?: number;
|
||||
/** Give up after this many ms. Default 3600000 (1 hour). */
|
||||
timeoutMs?: number;
|
||||
/** Called on every poll with the latest state. */
|
||||
onUpdate?: (state: BridgeState) => void;
|
||||
/** AbortSignal to cancel waiting. */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
// ===================== Ghost Links =====================
|
||||
|
||||
export type GhostStatus =
|
||||
| 'awaiting_deposit'
|
||||
| 'funded'
|
||||
| 'claiming'
|
||||
| 'claimed'
|
||||
| 'refunding'
|
||||
| 'refunded'
|
||||
| 'failed'
|
||||
| 'expired';
|
||||
|
||||
/** Terminal states: a Ghost Link will not change after reaching one of these. */
|
||||
export const GHOST_TERMINAL_STATUSES: GhostStatus[] = ['claimed', 'refunded', 'failed', 'expired'];
|
||||
|
||||
export interface GhostAmounts {
|
||||
/** Selectable Ghost Link amounts, in SOL. */
|
||||
amounts: number[];
|
||||
/** Fee in basis points (50 = 0.5%). */
|
||||
feeBps: number;
|
||||
/** Days a funded link stays claimable before auto refund. */
|
||||
claimExpiryDays: number;
|
||||
}
|
||||
|
||||
export interface CreateGhostLinkParams {
|
||||
/** Amount the receiver claims. Must be one of the allowed amounts. */
|
||||
amount: number;
|
||||
/** Address the funds return to if the link is never claimed. */
|
||||
refundAddress: string;
|
||||
/** Optional note shown to the receiver (max 140 chars). */
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface GhostLink {
|
||||
id: string;
|
||||
/** Amount the receiver claims, in SOL. */
|
||||
amountSol: number;
|
||||
/** Amount the sender must deposit (amount + fee), in SOL. */
|
||||
depositSol: number;
|
||||
feeSol: number;
|
||||
/** Freshly generated address to fund the link. */
|
||||
depositAddress: string;
|
||||
note: string;
|
||||
status: GhostStatus;
|
||||
claimable: boolean;
|
||||
expiresAt: string;
|
||||
claimExpiresAt: string | null;
|
||||
}
|
||||
|
||||
export interface GhostLinkState extends GhostLink {
|
||||
depositedSol: number;
|
||||
claimAddress: string | null;
|
||||
payoutSignature: string | null;
|
||||
refundSignature: string | null;
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** Result of creating a Ghost Link. Keep `secret`/`claimUrl` safe — they are never recoverable. */
|
||||
export interface CreatedGhostLink {
|
||||
link: GhostLink;
|
||||
/** The claim secret. Lives only here and in the claim URL — never sent to or stored by the server. */
|
||||
secret: string;
|
||||
/** The full shareable URL, including the secret in the fragment. */
|
||||
claimUrl: string;
|
||||
}
|
||||
|
||||
export interface GhostWaitOptions {
|
||||
intervalMs?: number;
|
||||
timeoutMs?: number;
|
||||
onUpdate?: (state: GhostLinkState) => void;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
const toHex = (bytes: Uint8Array) =>
|
||||
Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
/**
|
||||
* Generate a random claim secret and its SHA-256 hash. Only the hash is sent to
|
||||
* the server; the secret stays client side and belongs in the URL fragment.
|
||||
* Uses the Web Crypto API (available in browsers and Node 20+).
|
||||
*/
|
||||
export async function generateClaimSecret(): Promise<{ secret: string; secretHash: string }> {
|
||||
const c = globalThis.crypto;
|
||||
if (!c?.subtle) {
|
||||
throw new AlbatrossError('Web Crypto API not available (needs a browser or Node 20+)');
|
||||
}
|
||||
const bytes = new Uint8Array(32);
|
||||
c.getRandomValues(bytes);
|
||||
const secret = toHex(bytes);
|
||||
const digest = await c.subtle.digest('SHA-256', new TextEncoder().encode(secret));
|
||||
return { secret, secretHash: toHex(new Uint8Array(digest)) };
|
||||
}
|
||||
|
||||
export class AlbatrossError extends Error {
|
||||
status?: number;
|
||||
constructor(message: string, status?: number) {
|
||||
super(message);
|
||||
this.name = 'AlbatrossError';
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
const sleep = (ms: number, signal?: AbortSignal) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const t = setTimeout(resolve, ms);
|
||||
signal?.addEventListener('abort', () => {
|
||||
clearTimeout(t);
|
||||
reject(new AlbatrossError('Aborted'));
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* Albatross API client.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Albatross } from '@usealbatross/sdk'
|
||||
*
|
||||
* const albatross = new Albatross()
|
||||
*
|
||||
* const bridge = await albatross.createBridge({ amount: 1, destination: 'YourWallet…' })
|
||||
* console.log('Send', bridge.depositSol, 'SOL to', bridge.depositAddress)
|
||||
*
|
||||
* const result = await albatross.waitForBridge(bridge.id, {
|
||||
* onUpdate: (s) => console.log(s.status),
|
||||
* })
|
||||
* console.log('Done:', result.payoutSignature)
|
||||
* ```
|
||||
*/
|
||||
export class Albatross {
|
||||
private baseUrl: string;
|
||||
private appUrl: string;
|
||||
private _fetch: typeof fetch;
|
||||
|
||||
constructor(options: AlbatrossOptions = {}) {
|
||||
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
||||
this.appUrl = (options.appUrl ?? DEFAULT_APP_URL).replace(/\/$/, '');
|
||||
const f = options.fetch ?? globalThis.fetch;
|
||||
if (!f) {
|
||||
throw new AlbatrossError(
|
||||
'No fetch implementation found. Pass one via options.fetch (Node < 18).'
|
||||
);
|
||||
}
|
||||
this._fetch = f.bind(globalThis);
|
||||
}
|
||||
|
||||
private async req<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await this._fetch(this.baseUrl + path, {
|
||||
...init,
|
||||
headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) },
|
||||
});
|
||||
let data: any = null;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (!res.ok) {
|
||||
throw new AlbatrossError(data?.error ?? `Request failed (${res.status})`, res.status);
|
||||
}
|
||||
return data as T;
|
||||
}
|
||||
|
||||
/** Public network statistics. */
|
||||
getStats(): Promise<Stats> {
|
||||
return this.req<Stats>('/stats');
|
||||
}
|
||||
|
||||
/** The selectable bridge amounts and current fee. */
|
||||
getAmounts(): Promise<Amounts> {
|
||||
return this.req<Amounts>('/bridge/amounts');
|
||||
}
|
||||
|
||||
/** Create a new private bridge and get a fresh deposit address. */
|
||||
createBridge(params: CreateBridgeParams): Promise<Bridge> {
|
||||
if (!params?.amount) throw new AlbatrossError('amount is required');
|
||||
if (!params?.destination) throw new AlbatrossError('destination is required');
|
||||
return this.req<Bridge>('/bridge', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
amountSol: params.amount,
|
||||
destinationAddress: params.destination,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Fetch the current state of a bridge. */
|
||||
getBridge(id: string): Promise<BridgeState> {
|
||||
if (!id) throw new AlbatrossError('id is required');
|
||||
return this.req<BridgeState>(`/bridge/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll a bridge until it reaches a terminal state (completed, refunded,
|
||||
* failed or expired), then resolve with the final state.
|
||||
*/
|
||||
async waitForBridge(id: string, opts: WaitOptions = {}): Promise<BridgeState> {
|
||||
const interval = opts.intervalMs ?? 5000;
|
||||
const timeout = opts.timeoutMs ?? 3_600_000;
|
||||
const start = Date.now();
|
||||
// Date.now is fine at runtime for a client SDK.
|
||||
for (;;) {
|
||||
const state = await this.getBridge(id);
|
||||
opts.onUpdate?.(state);
|
||||
if (TERMINAL_STATUSES.includes(state.status)) return state;
|
||||
if (Date.now() - start > timeout) {
|
||||
throw new AlbatrossError('Timed out waiting for bridge to complete');
|
||||
}
|
||||
await sleep(interval, opts.signal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One shot helper: create a bridge and return it with a `wait()` function.
|
||||
* @example
|
||||
* const { bridge, wait } = await albatross.bridge({ amount: 1, destination })
|
||||
* console.log('send', bridge.depositSol, 'to', bridge.depositAddress)
|
||||
* const done = await wait()
|
||||
*/
|
||||
async bridge(params: CreateBridgeParams): Promise<{ bridge: Bridge; wait: (o?: WaitOptions) => Promise<BridgeState> }> {
|
||||
const bridge = await this.createBridge(params);
|
||||
return { bridge, wait: (o?: WaitOptions) => this.waitForBridge(bridge.id, o) };
|
||||
}
|
||||
|
||||
// ===================== Ghost Links =====================
|
||||
|
||||
/** The selectable Ghost Link amounts, fee, and claim expiry. */
|
||||
getGhostAmounts(): Promise<GhostAmounts> {
|
||||
return this.req<GhostAmounts>('/ghost/amounts');
|
||||
}
|
||||
|
||||
/** Build the shareable claim URL for a link id and secret. */
|
||||
buildClaimUrl(id: string, secret: string): string {
|
||||
return `${this.appUrl}/g/${id}#${secret}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Ghost Link. Generates a claim secret locally, sends only its hash,
|
||||
* and returns the link plus the secret and the full shareable claim URL.
|
||||
*
|
||||
* IMPORTANT: the secret and claimUrl are never stored by the server and cannot
|
||||
* be recovered. Persist or share them immediately.
|
||||
*/
|
||||
async createGhostLink(params: CreateGhostLinkParams): Promise<CreatedGhostLink> {
|
||||
if (!params?.amount) throw new AlbatrossError('amount is required');
|
||||
if (!params?.refundAddress) throw new AlbatrossError('refundAddress is required');
|
||||
const { secret, secretHash } = await generateClaimSecret();
|
||||
const link = await this.req<GhostLink>('/ghost', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
amountSol: params.amount,
|
||||
refundAddress: params.refundAddress,
|
||||
secretHash,
|
||||
note: params.note,
|
||||
}),
|
||||
});
|
||||
return { link, secret, claimUrl: this.buildClaimUrl(link.id, secret) };
|
||||
}
|
||||
|
||||
/** Fetch the current state of a Ghost Link. */
|
||||
getGhostLink(id: string): Promise<GhostLinkState> {
|
||||
if (!id) throw new AlbatrossError('id is required');
|
||||
return this.req<GhostLinkState>(`/ghost/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a Ghost Link to any wallet. The secret comes from the claim URL's
|
||||
* fragment. The payout is sent from an unrelated wallet by the protocol.
|
||||
*/
|
||||
claimGhostLink(params: { id: string; secret: string; destination: string }): Promise<{ ok: boolean; status: GhostStatus; id: string }> {
|
||||
if (!params?.id) throw new AlbatrossError('id is required');
|
||||
if (!params?.secret) throw new AlbatrossError('secret is required');
|
||||
if (!params?.destination) throw new AlbatrossError('destination is required');
|
||||
return this.req(`/ghost/${params.id}/claim`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ secret: params.secret, destinationAddress: params.destination }),
|
||||
});
|
||||
}
|
||||
|
||||
/** Poll a Ghost Link until it reaches a terminal state, then resolve. */
|
||||
async waitForGhostLink(id: string, opts: GhostWaitOptions = {}): Promise<GhostLinkState> {
|
||||
const interval = opts.intervalMs ?? 5000;
|
||||
const timeout = opts.timeoutMs ?? 3_600_000;
|
||||
const start = Date.now();
|
||||
for (;;) {
|
||||
const state = await this.getGhostLink(id);
|
||||
opts.onUpdate?.(state);
|
||||
if (GHOST_TERMINAL_STATUSES.includes(state.status)) return state;
|
||||
if (Date.now() - start > timeout) {
|
||||
throw new AlbatrossError('Timed out waiting for Ghost Link');
|
||||
}
|
||||
await sleep(interval, opts.signal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One shot helper: parse a claim URL and claim it to a destination wallet.
|
||||
* Accepts a full URL (…/g/<id>#<secret>) and extracts id + secret.
|
||||
*/
|
||||
async claimFromUrl(claimUrl: string, destination: string): Promise<{ ok: boolean; status: GhostStatus; id: string }> {
|
||||
const m = claimUrl.match(/\/g\/([^/#?]+)#(.+)$/);
|
||||
if (!m) throw new AlbatrossError('Invalid Ghost Link URL');
|
||||
return this.claimGhostLink({ id: m[1], secret: m[2], destination });
|
||||
}
|
||||
}
|
||||
|
||||
export default Albatross;
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user