Skip to content

Commit a9495ce

Browse files
authored
Merge pull request #6 from Short-io/ci/tests-and-workflow
Ci/tests and workflow
2 parents 51059f4 + dac0236 commit a9495ce

File tree

14 files changed

+3009
-3906
lines changed

14 files changed

+3009
-3906
lines changed

.github/workflows/test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-node@v4
15+
with:
16+
node-version: 22
17+
cache: npm
18+
- run: npm ci
19+
- run: npx playwright install chromium --with-deps
20+
- run: npm run typecheck
21+
- run: npm run lint
22+
- run: npm test
23+
- run: npm run build

CLAUDE.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project
6+
7+
Browser SDK for Short.io API (`@short.io/client-browser`). Zero runtime dependencies, browser-first, public key auth only.
8+
9+
## Commands
10+
11+
```bash
12+
npm run build # Rollup build → CJS, ESM, UMD in dist/
13+
npm run dev # Rollup watch mode
14+
npm test # Vitest with jsdom
15+
npm run test:watch # Vitest watch mode
16+
npm run typecheck # tsc --noEmit
17+
npm run lint # eslint src/**/*.ts
18+
```
19+
20+
Run a single test: `npx vitest run --testPathPattern='shortio.test'`
21+
22+
## Architecture
23+
24+
Four source files in `src/`:
25+
26+
- **types.ts** — All TypeScript interfaces (`ShortioConfig`, `CreateLinkRequest/Response`, `ExpandLinkRequest/Response`, `ConversionTrackingOptions/Result`, `ApiError`)
27+
- **shortio.ts** — Core implementation: `ShortioClient` class and `createSecure()` encryption utility
28+
- **index.ts** — Public API surface: re-exports `ShortioClient`, `createClient()` factory, and all types
29+
- **`__tests__/shortio.test.ts`** — Full test suite with mocked `fetch`, `navigator.sendBeacon`, and `crypto.subtle`
30+
31+
### Key patterns
32+
33+
- `ShortioClient` wraps all HTTP calls with public key auth via `Authorization` header
34+
- Encrypted links use Web Crypto API (AES-GCM) — key goes in URL fragment, not sent to server
35+
- Conversion tracking uses `navigator.sendBeacon()` with `clid` query parameter from current URL
36+
- Three build outputs via Rollup: CJS (`dist/index.js`), ESM (`dist/index.esm.js`), UMD (`dist/index.umd.js` as global `ShortioClient`)
37+
38+
## Conventions
39+
40+
- TypeScript strict mode with `noUnusedLocals` and `noUnusedParameters`
41+
- ESLint v9 config requires nullish coalescing (`??`) and optional chaining (`?.`)
42+
- Explicit return types on functions (warning level)
43+
- No `any` usage (warning level, relaxed in tests)
44+
- Tests mock browser APIs (fetch, crypto.subtle, sendBeacon, TextEncoder) since Vitest runs in jsdom

README.md

Lines changed: 159 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
# Short.io Browser SDK
22

3-
A lightweight TypeScript/JavaScript SDK for Short.io's URL shortening API, designed specifically for browser environments with public key authentication.
4-
5-
## Features
6-
7-
- 🌐 **Browser-first**: Optimized for client-side applications
8-
- 🔑 **Public Key Auth**: Works with Short.io public API keys
9-
- 📦 **Lightweight**: Minimal dependencies, small bundle size
10-
- 🎯 **TypeScript**: Full type safety with TypeScript support
11-
- 🚀 **Modern**: Uses fetch API and ES modules
3+
A lightweight TypeScript/JavaScript SDK for [Short.io](https://short.io)'s URL shortening API, designed for browser environments with public key authentication. Zero runtime dependencies.
124

135
## Installation
146

@@ -21,7 +13,6 @@ npm install @short.io/client-browser
2113
```typescript
2214
import { createClient } from '@short.io/client-browser';
2315

24-
// Initialize the client with your public API key
2516
const client = createClient({
2617
publicKey: 'your-public-api-key'
2718
});
@@ -31,15 +22,13 @@ const link = await client.createLink({
3122
originalURL: 'https://example.com/very-long-url',
3223
domain: 'your-domain.com'
3324
});
34-
3525
console.log(link.shortURL); // https://your-domain.com/abc123
3626

3727
// Expand a short link
3828
const expanded = await client.expandLink({
3929
domain: 'your-domain.com',
4030
path: 'abc123'
4131
});
42-
4332
console.log(expanded.originalURL); // https://example.com/very-long-url
4433
```
4534

@@ -49,97 +38,161 @@ console.log(expanded.originalURL); // https://example.com/very-long-url
4938

5039
Creates a new Short.io client instance.
5140

52-
**Parameters:**
53-
- `config.publicKey` (string): Your Short.io public API key
54-
- `config.baseUrl` (string, optional): Custom API base URL (defaults to `https://api.short.io/links`)
41+
| Parameter | Type | Required | Description |
42+
|-----------|------|----------|-------------|
43+
| `config.publicKey` | `string` | Yes | Your Short.io public API key |
44+
| `config.baseUrl` | `string` | No | Custom API base URL (default: `https://api.short.io`) |
5545

5646
### `client.createLink(request)`
5747

5848
Creates a new short link.
5949

60-
**Parameters:**
61-
- `request.originalURL` (string): The URL to shorten
62-
- `request.domain` (string): Your Short.io domain
63-
- `request.path` (string, optional): Custom path for the short link
64-
- `request.title` (string, optional): Title for the link
65-
- `request.tags` (string[], optional): Tags for organization
66-
- `request.allowDuplicates` (boolean, optional): Allow duplicate links
67-
68-
**Returns:** Promise<CreateLinkResponse>
50+
| Parameter | Type | Required | Description |
51+
|-----------|------|----------|-------------|
52+
| `domain` | `string` | Yes | Your Short.io domain |
53+
| `originalURL` | `string` | Yes | The URL to shorten |
54+
| `path` | `string` | No | Custom path for the short link |
55+
| `title` | `string` | No | Link title |
56+
| `tags` | `string[]` | No | Tags for organization |
57+
| `folderId` | `string` | No | Folder ID |
58+
| `cloaking` | `boolean` | No | Enable link cloaking |
59+
| `password` | `string` | No | Password-protect the link |
60+
| `passwordContact` | `boolean` | No | Require contact info with password |
61+
| `redirectType` | `301 \| 302 \| 307 \| 308` | No | HTTP redirect status code |
62+
| `utmSource` | `string` | No | UTM source parameter |
63+
| `utmMedium` | `string` | No | UTM medium parameter |
64+
| `utmCampaign` | `string` | No | UTM campaign parameter |
65+
| `utmContent` | `string` | No | UTM content parameter |
66+
| `utmTerm` | `string` | No | UTM term parameter |
67+
| `androidURL` | `string` | No | Redirect URL for Android devices |
68+
| `iphoneURL` | `string` | No | Redirect URL for iPhones |
69+
| `clicksLimit` | `number` | No | Maximum number of clicks allowed |
70+
| `skipQS` | `boolean` | No | Skip query string forwarding |
71+
| `archived` | `boolean` | No | Create link as archived |
72+
| `splitURL` | `string` | No | A/B test destination URL |
73+
| `splitPercent` | `number` | No | A/B test traffic split percentage |
74+
| `integrationAdroll` | `string` | No | AdRoll integration pixel |
75+
| `integrationFB` | `string` | No | Facebook integration pixel |
76+
| `integrationGA` | `string` | No | Google Analytics integration |
77+
| `integrationGTM` | `string` | No | Google Tag Manager integration |
78+
79+
Returns `Promise<CreateLinkResponse>` with the full link object including `shortURL`, `secureShortURL`, `id`, and all configured properties.
6980

7081
### `client.expandLink(request)`
7182

7283
Expands a short link to get its details.
7384

74-
**Parameters:**
75-
- `request.domain` (string): The Short.io domain
76-
- `request.path` (string): The path of the short link
85+
| Parameter | Type | Required | Description |
86+
|-----------|------|----------|-------------|
87+
| `domain` | `string` | Yes | The Short.io domain |
88+
| `path` | `string` | Yes | The path of the short link |
89+
90+
Returns `Promise<ExpandLinkResponse>` (same shape as `CreateLinkResponse`).
7791

78-
**Returns:** Promise<ExpandLinkResponse>
92+
### `client.createEncryptedLink(request)`
7993

80-
## Usage Examples
94+
Creates an encrypted short link using AES-GCM encryption. The original URL is encrypted client-side before being sent to the API; the decryption key is placed in the URL fragment (hash) and never sent to the server.
8195

82-
### Basic Link Creation
96+
Takes the same parameters as `createLink`. Returns `Promise<CreateLinkResponse>` where `shortURL` includes the `#key` fragment for decryption.
8397

8498
```typescript
85-
const link = await client.createLink({
86-
originalURL: 'https://github.com/Short-io/client-browser',
87-
domain: 'your-domain.com',
88-
title: 'Short.io Browser SDK'
99+
const link = await client.createEncryptedLink({
100+
originalURL: 'https://sensitive-content.example.com/private',
101+
domain: 'your-domain.com'
89102
});
103+
// link.shortURL → https://your-domain.com/abc123#<base64-key>
90104
```
91105

92-
### Custom Path
106+
### `client.trackConversion(options)`
107+
108+
Tracks a conversion event using `navigator.sendBeacon()`. Reads the `clid` (click ID) query parameter from the current page URL to attribute the conversion.
109+
110+
| Parameter | Type | Required | Description |
111+
|-----------|------|----------|-------------|
112+
| `domain` | `string` | Yes | Your Short.io domain |
113+
| `conversionId` | `string` | No | Custom conversion identifier |
114+
| `value` | `number` | No | Monetary or numeric value for the conversion |
115+
116+
Returns `ConversionTrackingResult`:
93117

94118
```typescript
95-
const link = await client.createLink({
96-
originalURL: 'https://docs.short.io',
97-
domain: 'your-domain.com',
98-
path: 'docs'
99-
});
119+
{
120+
success: boolean; // true if clid was found and beacon sent
121+
conversionId?: string;
122+
clid?: string;
123+
domain: string;
124+
value?: number;
125+
}
100126
```
101127

102-
### With Tags
103-
104128
```typescript
105-
const link = await client.createLink({
106-
originalURL: 'https://example.com',
129+
const result = client.trackConversion({
107130
domain: 'your-domain.com',
108-
tags: ['marketing', 'campaign-2024']
131+
conversionId: 'purchase',
132+
value: 49.99
109133
});
134+
135+
if (result.success) {
136+
console.log('Conversion tracked for click:', result.clid);
137+
}
110138
```
111139

112-
### Error Handling
140+
### `client.getClickId()`
141+
142+
Returns the `clid` query parameter from the current page URL, or `null` if not present.
113143

114144
```typescript
115-
try {
116-
const link = await client.createLink({
117-
originalURL: 'https://example.com',
118-
domain: 'your-domain.com'
119-
});
120-
} catch (error) {
121-
console.error('Failed to create link:', error.message);
122-
}
145+
const clickId = client.getClickId();
123146
```
124147

125-
## Browser Support
148+
### `client.observeConversions(options)`
126149

127-
This SDK works in all modern browsers that support:
128-
- Fetch API
129-
- ES2018 features
130-
- Promises/async-await
150+
Enables declarative conversion tracking via HTML `data-` attributes. Scans the DOM for elements with `data-shortio-conversion` and automatically binds event listeners. Also watches for dynamically added elements using a `MutationObserver`.
131151

132-
For older browsers, you may need polyfills for the fetch API.
152+
| Parameter | Type | Required | Description |
153+
|-----------|------|----------|-------------|
154+
| `domain` | `string` | Yes | Your Short.io domain |
133155

134-
## Bundle Formats
156+
Returns a `ConversionObserver` with a `disconnect()` method to remove all listeners and stop observing.
135157

136-
The SDK is available in multiple formats:
158+
**HTML attributes:**
137159

138-
- **ES Modules**: `dist/index.esm.js` (recommended for modern bundlers)
139-
- **CommonJS**: `dist/index.js` (Node.js compatibility)
140-
- **UMD**: `dist/index.umd.js` (direct browser usage)
160+
| Attribute | Description |
161+
|-----------|-------------|
162+
| `data-shortio-conversion` | Conversion identifier (empty string for no ID) |
163+
| `data-shortio-conversion-value` | Numeric value for the conversion (ignored if not a valid number) |
141164

142-
### Direct Browser Usage
165+
**Event binding by element type:**
166+
167+
| Element | Event |
168+
|---------|-------|
169+
| `<form>` | `submit` |
170+
| `<input>` (non-submit) | `change` |
171+
| `<button>`, `<a>`, `<input type="submit">` | `click` |
172+
173+
```html
174+
<!-- Declarative conversion tracking -->
175+
<form data-shortio-conversion="signup">...</form>
176+
<button data-shortio-conversion="purchase" data-shortio-conversion-value="29.99">Buy Now</button>
177+
<a href="/pricing" data-shortio-conversion="cta-click">View Pricing</a>
178+
```
179+
180+
```typescript
181+
const observer = client.observeConversions({ domain: 'your-domain.com' });
182+
183+
// Later, to stop tracking:
184+
observer.disconnect();
185+
```
186+
187+
## Bundle Formats
188+
189+
| Format | File | Use case |
190+
|--------|------|----------|
191+
| ES Modules | `dist/index.esm.js` | Modern bundlers (Vite, webpack, etc.) |
192+
| CommonJS | `dist/index.js` | Node.js / legacy bundlers |
193+
| UMD | `dist/index.umd.js` | Direct `<script>` tag usage |
194+
195+
### Direct Browser Usage (UMD)
143196

144197
```html
145198
<script src="https://unpkg.com/@short.io/client-browser/dist/index.umd.js"></script>
@@ -150,33 +203,56 @@ The SDK is available in multiple formats:
150203
</script>
151204
```
152205

153-
## Getting Your API Key
206+
## TypeScript
154207

155-
1. Visit your [Short.io dashboard](https://app.short.io)
156-
2. Go to Integrations & API
157-
3. Create a new public API key for your domain
208+
Full type definitions are included. All types are exported:
158209

159-
⚠️ **Security Note**: Public keys are safe to use in browser environments but have limited permissions. Never use private API keys in client-side code.
210+
```typescript
211+
import type {
212+
ShortioConfig,
213+
CreateLinkRequest,
214+
CreateLinkResponse,
215+
ExpandLinkRequest,
216+
ExpandLinkResponse,
217+
ConversionTrackingOptions,
218+
ConversionTrackingResult,
219+
ObserveConversionsOptions,
220+
ConversionObserver,
221+
ApiError
222+
} from '@short.io/client-browser';
223+
```
160224

161-
## TypeScript Support
225+
## Error Handling
162226

163-
The SDK includes full TypeScript definitions. All methods and responses are fully typed:
227+
API errors throw a standard `Error` with the message from the Short.io API response:
164228

165229
```typescript
166-
import type { CreateLinkRequest, CreateLinkResponse } from '@short.io/client-browser';
230+
try {
231+
const link = await client.createLink({
232+
originalURL: 'https://example.com',
233+
domain: 'your-domain.com'
234+
});
235+
} catch (error) {
236+
console.error('Failed to create link:', error.message);
237+
}
238+
```
167239

168-
const request: CreateLinkRequest = {
169-
originalURL: 'https://example.com',
170-
domain: 'your-domain.com'
171-
};
240+
## Getting Your API Key
172241

173-
const response: CreateLinkResponse = await client.createLink(request);
174-
```
242+
1. Visit your [Short.io dashboard](https://app.short.io)
243+
2. Go to **Integrations & API**
244+
3. Create a new **public** API key for your domain
245+
246+
> **Security:** Public keys are safe to use in browser environments but have limited permissions. Never use private API keys in client-side code.
175247
176-
## Contributing
248+
## Browser Support
177249

178-
Contributions are welcome! Please feel free to submit a Pull Request.
250+
Requires browsers supporting:
251+
- [Fetch API](https://caniuse.com/fetch)
252+
- [Web Crypto API](https://caniuse.com/cryptography) (for encrypted links)
253+
- [Beacon API](https://caniuse.com/beacon) (for conversion tracking)
254+
- [MutationObserver](https://caniuse.com/mutationobserver) (for declarative conversion tracking)
179255

180256
## License
181257

182-
MIT © Short.io
258+
MIT

0 commit comments

Comments
 (0)