@whatsmyfyi/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @whatsmyfyi/sdk
2
+
3
+ Official TypeScript SDK for the [whatsmy.fyi](https://whatsmy.fyi) IP geolocation API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @whatsmyfyi/sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { WhatsMyFyi } from '@whatsmyfyi/sdk'
15
+
16
+ const client = new WhatsMyFyi({ apiKey: 'wmf_your_key_here' })
17
+
18
+ // Full IP response
19
+ const data = await client.ip.lookup()
20
+ console.log(data.ip) // "203.0.113.42"
21
+ console.log(data.city) // "Amsterdam"
22
+ console.log(data.country) // "NL"
23
+
24
+ // Quick helpers
25
+ const ip = await client.ip.address() // "203.0.113.42"
26
+ const location = await client.ip.location() // { city, country, countryCode, lat, lng }
27
+ const org = await client.ip.org() // { asn, name }
28
+ ```
29
+
30
+ ## Get an API Key
31
+
32
+ Sign up at [whatsmy.fyi/signup](https://whatsmy.fyi/signup) and create a free API key in the dashboard. Free tier includes 10,000 requests/day.
33
+
34
+ ## Features
35
+
36
+ - TypeScript-first — full type definitions included
37
+ - Zero dependencies
38
+ - Automatic retry on 5xx errors (max 3 attempts, exponential backoff)
39
+ - Works in Node.js, Deno, Bun, and browser (fetch-based)
40
+ - Throws `WhatsMyFyiError` with `code`, `message`, and `status` fields
41
+
42
+ ## Error Handling
43
+
44
+ ```typescript
45
+ import { WhatsMyFyi, WhatsMyFyiError } from '@whatsmyfyi/sdk'
46
+
47
+ const client = new WhatsMyFyi({ apiKey: 'wmf_...' })
48
+
49
+ try {
50
+ const data = await client.ip.lookup()
51
+ } catch (err) {
52
+ if (err instanceof WhatsMyFyiError) {
53
+ console.error(err.code) // e.g. "rate_limit_exceeded"
54
+ console.error(err.status) // HTTP status code
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## License
60
+
61
+ MIT — [whatsmy.fyi](https://whatsmy.fyi)
@@ -0,0 +1,63 @@
1
+ interface IpResponse {
2
+ status: 'success';
3
+ ip: string | null;
4
+ ipv6: string | null;
5
+ city?: string | null;
6
+ region?: string | null;
7
+ region_code?: string | null;
8
+ country?: string | null;
9
+ country_name?: string | null;
10
+ continent?: string | null;
11
+ latitude?: number | null;
12
+ longitude?: number | null;
13
+ timezone?: string | null;
14
+ timezone_offset?: number | null;
15
+ postal_code?: string | null;
16
+ asn?: number | null;
17
+ org?: string | null;
18
+ as?: string | null;
19
+ is_eu?: boolean | null;
20
+ currency?: string | null;
21
+ http_protocol?: string | null;
22
+ tls_version?: string | null;
23
+ tls_cipher?: string | null;
24
+ rtt?: number | null;
25
+ colo?: string | null;
26
+ proxy?: boolean | null;
27
+ hosting?: boolean | null;
28
+ }
29
+ interface LocationResult {
30
+ city: string | null;
31
+ country: string | null;
32
+ countryCode: string | null;
33
+ lat: number | null;
34
+ lng: number | null;
35
+ }
36
+ interface OrgResult {
37
+ asn: number | null;
38
+ name: string | null;
39
+ }
40
+ interface WhatsMyFyiOptions {
41
+ apiKey: string;
42
+ baseUrl?: string;
43
+ }
44
+ declare class WhatsMyFyiError extends Error {
45
+ readonly code: string;
46
+ readonly status: number;
47
+ constructor(code: string, message: string, status: number);
48
+ }
49
+ declare class IpClient {
50
+ private readonly apiKey;
51
+ private readonly baseUrl;
52
+ constructor(apiKey: string, baseUrl: string);
53
+ lookup(): Promise<IpResponse>;
54
+ address(): Promise<string | null>;
55
+ location(): Promise<LocationResult>;
56
+ org(): Promise<OrgResult>;
57
+ }
58
+ declare class WhatsMyFyi {
59
+ readonly ip: IpClient;
60
+ constructor({ apiKey, baseUrl }: WhatsMyFyiOptions);
61
+ }
62
+
63
+ export { type IpResponse, type LocationResult, type OrgResult, WhatsMyFyi, WhatsMyFyiError, type WhatsMyFyiOptions };
@@ -0,0 +1,63 @@
1
+ interface IpResponse {
2
+ status: 'success';
3
+ ip: string | null;
4
+ ipv6: string | null;
5
+ city?: string | null;
6
+ region?: string | null;
7
+ region_code?: string | null;
8
+ country?: string | null;
9
+ country_name?: string | null;
10
+ continent?: string | null;
11
+ latitude?: number | null;
12
+ longitude?: number | null;
13
+ timezone?: string | null;
14
+ timezone_offset?: number | null;
15
+ postal_code?: string | null;
16
+ asn?: number | null;
17
+ org?: string | null;
18
+ as?: string | null;
19
+ is_eu?: boolean | null;
20
+ currency?: string | null;
21
+ http_protocol?: string | null;
22
+ tls_version?: string | null;
23
+ tls_cipher?: string | null;
24
+ rtt?: number | null;
25
+ colo?: string | null;
26
+ proxy?: boolean | null;
27
+ hosting?: boolean | null;
28
+ }
29
+ interface LocationResult {
30
+ city: string | null;
31
+ country: string | null;
32
+ countryCode: string | null;
33
+ lat: number | null;
34
+ lng: number | null;
35
+ }
36
+ interface OrgResult {
37
+ asn: number | null;
38
+ name: string | null;
39
+ }
40
+ interface WhatsMyFyiOptions {
41
+ apiKey: string;
42
+ baseUrl?: string;
43
+ }
44
+ declare class WhatsMyFyiError extends Error {
45
+ readonly code: string;
46
+ readonly status: number;
47
+ constructor(code: string, message: string, status: number);
48
+ }
49
+ declare class IpClient {
50
+ private readonly apiKey;
51
+ private readonly baseUrl;
52
+ constructor(apiKey: string, baseUrl: string);
53
+ lookup(): Promise<IpResponse>;
54
+ address(): Promise<string | null>;
55
+ location(): Promise<LocationResult>;
56
+ org(): Promise<OrgResult>;
57
+ }
58
+ declare class WhatsMyFyi {
59
+ readonly ip: IpClient;
60
+ constructor({ apiKey, baseUrl }: WhatsMyFyiOptions);
61
+ }
62
+
63
+ export { type IpResponse, type LocationResult, type OrgResult, WhatsMyFyi, WhatsMyFyiError, type WhatsMyFyiOptions };
package/dist/index.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ WhatsMyFyi: () => WhatsMyFyi,
24
+ WhatsMyFyiError: () => WhatsMyFyiError
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var DEFAULT_BASE_URL = "https://whatsmy.fyi";
28
+ var MAX_RETRIES = 3;
29
+ var WhatsMyFyiError = class extends Error {
30
+ constructor(code, message, status) {
31
+ super(message);
32
+ this.name = "WhatsMyFyiError";
33
+ this.code = code;
34
+ this.status = status;
35
+ }
36
+ };
37
+ async function fetchWithRetry(url, apiKey, attempt = 1) {
38
+ const res = await fetch(url, {
39
+ headers: { Authorization: `Bearer ${apiKey}` }
40
+ });
41
+ if (res.status >= 500 && attempt < MAX_RETRIES) {
42
+ const delay = Math.pow(2, attempt) * 100;
43
+ await new Promise((r) => setTimeout(r, delay));
44
+ return fetchWithRetry(url, apiKey, attempt + 1);
45
+ }
46
+ if (!res.ok) {
47
+ let code = "internal_error";
48
+ let message = `HTTP ${res.status}`;
49
+ try {
50
+ const body = await res.json();
51
+ if (body.error) code = body.error;
52
+ if (body.message) message = body.message;
53
+ } catch {
54
+ }
55
+ throw new WhatsMyFyiError(code, message, res.status);
56
+ }
57
+ return res.json();
58
+ }
59
+ var IpClient = class {
60
+ constructor(apiKey, baseUrl) {
61
+ this.apiKey = apiKey;
62
+ this.baseUrl = baseUrl;
63
+ }
64
+ async lookup() {
65
+ return fetchWithRetry(`${this.baseUrl}/api/v1/ip`, this.apiKey);
66
+ }
67
+ async address() {
68
+ const data = await this.lookup();
69
+ return data.ip ?? data.ipv6 ?? null;
70
+ }
71
+ async location() {
72
+ const data = await this.lookup();
73
+ return {
74
+ city: data.city ?? null,
75
+ country: data.country_name ?? null,
76
+ countryCode: data.country ?? null,
77
+ lat: data.latitude ?? null,
78
+ lng: data.longitude ?? null
79
+ };
80
+ }
81
+ async org() {
82
+ const data = await this.lookup();
83
+ return {
84
+ asn: data.asn ?? null,
85
+ name: data.org ?? null
86
+ };
87
+ }
88
+ };
89
+ var WhatsMyFyi = class {
90
+ constructor({ apiKey, baseUrl = DEFAULT_BASE_URL }) {
91
+ if (!apiKey) throw new Error("apiKey is required");
92
+ this.ip = new IpClient(apiKey, baseUrl.replace(/\/$/, ""));
93
+ }
94
+ };
95
+ // Annotate the CommonJS export names for ESM import in node:
96
+ 0 && (module.exports = {
97
+ WhatsMyFyi,
98
+ WhatsMyFyiError
99
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,73 @@
1
+ // src/index.ts
2
+ var DEFAULT_BASE_URL = "https://whatsmy.fyi";
3
+ var MAX_RETRIES = 3;
4
+ var WhatsMyFyiError = class extends Error {
5
+ constructor(code, message, status) {
6
+ super(message);
7
+ this.name = "WhatsMyFyiError";
8
+ this.code = code;
9
+ this.status = status;
10
+ }
11
+ };
12
+ async function fetchWithRetry(url, apiKey, attempt = 1) {
13
+ const res = await fetch(url, {
14
+ headers: { Authorization: `Bearer ${apiKey}` }
15
+ });
16
+ if (res.status >= 500 && attempt < MAX_RETRIES) {
17
+ const delay = Math.pow(2, attempt) * 100;
18
+ await new Promise((r) => setTimeout(r, delay));
19
+ return fetchWithRetry(url, apiKey, attempt + 1);
20
+ }
21
+ if (!res.ok) {
22
+ let code = "internal_error";
23
+ let message = `HTTP ${res.status}`;
24
+ try {
25
+ const body = await res.json();
26
+ if (body.error) code = body.error;
27
+ if (body.message) message = body.message;
28
+ } catch {
29
+ }
30
+ throw new WhatsMyFyiError(code, message, res.status);
31
+ }
32
+ return res.json();
33
+ }
34
+ var IpClient = class {
35
+ constructor(apiKey, baseUrl) {
36
+ this.apiKey = apiKey;
37
+ this.baseUrl = baseUrl;
38
+ }
39
+ async lookup() {
40
+ return fetchWithRetry(`${this.baseUrl}/api/v1/ip`, this.apiKey);
41
+ }
42
+ async address() {
43
+ const data = await this.lookup();
44
+ return data.ip ?? data.ipv6 ?? null;
45
+ }
46
+ async location() {
47
+ const data = await this.lookup();
48
+ return {
49
+ city: data.city ?? null,
50
+ country: data.country_name ?? null,
51
+ countryCode: data.country ?? null,
52
+ lat: data.latitude ?? null,
53
+ lng: data.longitude ?? null
54
+ };
55
+ }
56
+ async org() {
57
+ const data = await this.lookup();
58
+ return {
59
+ asn: data.asn ?? null,
60
+ name: data.org ?? null
61
+ };
62
+ }
63
+ };
64
+ var WhatsMyFyi = class {
65
+ constructor({ apiKey, baseUrl = DEFAULT_BASE_URL }) {
66
+ if (!apiKey) throw new Error("apiKey is required");
67
+ this.ip = new IpClient(apiKey, baseUrl.replace(/\/$/, ""));
68
+ }
69
+ };
70
+ export {
71
+ WhatsMyFyi,
72
+ WhatsMyFyiError
73
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@whatsmyfyi/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for the whatsmy.fyi IP geolocation API",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "files": ["dist", "README.md"],
20
+ "keywords": ["ip", "geolocation", "api", "whatsmy.fyi", "ip-address", "ip-lookup"],
21
+ "author": "whatsmy.fyi",
22
+ "homepage": "https://whatsmy.fyi",
23
+ "license": "MIT",
24
+ "devDependencies": {
25
+ "tsup": "^8.5.1",
26
+ "typescript": "^5.0.0"
27
+ }
28
+ }