mobbin-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yabbal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # mobbin-sdk
2
+
3
+ > Unofficial TypeScript SDK for the [Mobbin](https://mobbin.com/) API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add mobbin-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { MobbinClient } from "mobbin-sdk";
15
+
16
+ const client = new MobbinClient({
17
+ credentials: { email: "user@example.com", password: "..." },
18
+ });
19
+ await client.init();
20
+
21
+ // Search screens
22
+ const screens = await client.screens.search(
23
+ { platform: "ios", screenPatterns: ["Login", "Signup"] },
24
+ { pageSize: 10, sortBy: "trending" },
25
+ );
26
+
27
+ // List apps
28
+ const apps = await client.apps.list("ios");
29
+
30
+ // Popular apps
31
+ const popular = await client.apps.popular("ios");
32
+
33
+ // Filters
34
+ const filters = await client.filters.list();
35
+
36
+ // Download a screen image
37
+ const buffer = await client.screens.download(
38
+ screens.data[0].screenCdnImgSources.src,
39
+ );
40
+ ```
41
+
42
+ ## License
43
+
44
+ MIT
@@ -0,0 +1,330 @@
1
+ type Platform = "ios" | "android" | "web";
2
+ type SortBy = "trending" | "publishedAt" | "popularity";
3
+ type ContentType = "apps" | "screens" | "flows";
4
+ interface PaginationOptions {
5
+ pageSize: number;
6
+ sortBy: SortBy;
7
+ cursor?: string;
8
+ }
9
+ interface ScreenFilterOptions {
10
+ platform: Platform;
11
+ screenPatterns?: string[] | null;
12
+ screenElements?: string[] | null;
13
+ screenKeywords?: string | null;
14
+ appCategories?: string[] | null;
15
+ hasAnimation?: boolean | null;
16
+ }
17
+ interface AppFilterOptions {
18
+ platform: Platform;
19
+ appCategories?: string[] | null;
20
+ }
21
+ interface ScreenCdnImgSource {
22
+ src: string;
23
+ srcSet: string;
24
+ downloadableSrc: string;
25
+ }
26
+ interface ScreenMetadata {
27
+ width: number;
28
+ height: number;
29
+ }
30
+ interface Screen {
31
+ id: string;
32
+ screenUrl: string;
33
+ screenNumber: number;
34
+ screenPatterns: string[];
35
+ screenElements: string[];
36
+ screenKeywords: string[];
37
+ appVersionId: string;
38
+ appId: string;
39
+ appName: string;
40
+ appCategory: string;
41
+ appLogoUrl: string;
42
+ platform: Platform;
43
+ popularityMetric: number;
44
+ trendingMetric: number;
45
+ metadata: ScreenMetadata;
46
+ screenCdnImgSources: ScreenCdnImgSource;
47
+ }
48
+ interface App {
49
+ id: string;
50
+ appName: string;
51
+ appCategory: string;
52
+ appLogoUrl: string;
53
+ appTagline: string;
54
+ platform: Platform;
55
+ keywords: string[];
56
+ createdAt: string;
57
+ appVersionId: string;
58
+ appVersionPublishedAt: string;
59
+ previewScreens: string[];
60
+ previewVideoUrl: string | null;
61
+ allAppCategories: string[];
62
+ popularityMetric: number;
63
+ trendingMetric: number;
64
+ isRestricted: boolean;
65
+ }
66
+ interface PopularApp {
67
+ app_id: string;
68
+ app_name: string;
69
+ app_logo_url: string;
70
+ preview_screens: string[];
71
+ app_category: string;
72
+ popularity_metric: number;
73
+ }
74
+ interface SearchableApp {
75
+ id: string;
76
+ platform: Platform;
77
+ appName: string;
78
+ appLogoUrl: string;
79
+ appTagline: string;
80
+ keywords: string[];
81
+ previewScreens: string[];
82
+ }
83
+ interface TrendingApp {
84
+ id: string;
85
+ platform: Platform;
86
+ appName: string;
87
+ appLogoUrl: string;
88
+ trending_metric: number;
89
+ }
90
+ interface DictionaryEntry {
91
+ id: string;
92
+ displayName: string;
93
+ definition: string;
94
+ synonyms: string[];
95
+ subCategory: string;
96
+ exampleScreens: string[];
97
+ contentCounts?: Record<string, number>;
98
+ }
99
+ interface DictionaryCategory {
100
+ slug: string;
101
+ displayName: string;
102
+ experience: "web" | "mobile";
103
+ subCategories: {
104
+ displayName: string;
105
+ entries: DictionaryEntry[];
106
+ }[];
107
+ }
108
+ interface TrendingFilterTag {
109
+ imageUrl: string;
110
+ cardType: string;
111
+ order: number;
112
+ dictionaryEntry: DictionaryEntry;
113
+ }
114
+ interface SearchableSite {
115
+ id: string;
116
+ name: string;
117
+ logo_url: string;
118
+ tagline: string;
119
+ keywords: string[];
120
+ }
121
+ interface Collection {
122
+ id: string;
123
+ name: string;
124
+ [key: string]: unknown;
125
+ }
126
+ interface SupabaseSession {
127
+ access_token: string;
128
+ refresh_token: string;
129
+ expires_at: number;
130
+ expires_in: number;
131
+ user: {
132
+ id: string;
133
+ email: string;
134
+ [key: string]: unknown;
135
+ };
136
+ }
137
+ interface FetchScreensResponse {
138
+ value: {
139
+ searchRequestId: string;
140
+ data: Screen[];
141
+ };
142
+ }
143
+ interface SearchAppsResponse {
144
+ value: {
145
+ searchRequestId: string;
146
+ data: App[];
147
+ };
148
+ }
149
+ interface PopularAppsResponse {
150
+ value: PopularApp[];
151
+ }
152
+ interface DictionaryResponse {
153
+ value: DictionaryCategory[];
154
+ }
155
+ interface TrendingAppsResponse {
156
+ value: TrendingApp[];
157
+ }
158
+ interface TrendingFilterTagsResponse {
159
+ value: TrendingFilterTag[];
160
+ }
161
+ interface SearchableSitesResponse {
162
+ value: SearchableSite[];
163
+ }
164
+ interface CollectionsResponse {
165
+ value: Collection[];
166
+ }
167
+ interface SavedContentsResponse {
168
+ value: unknown[];
169
+ }
170
+
171
+ interface FetchOptions {
172
+ getAccessToken: () => string | undefined;
173
+ getSession: () => Record<string, unknown> | undefined;
174
+ }
175
+ declare const createFetch: (options: FetchOptions) => {
176
+ request: <T>(path: string, init?: RequestInit & {
177
+ baseUrl?: string;
178
+ }) => Promise<T>;
179
+ };
180
+
181
+ declare abstract class Resource {
182
+ protected readonly fetch: ReturnType<typeof createFetch>;
183
+ constructor(fetch: ReturnType<typeof createFetch>);
184
+ }
185
+
186
+ declare class AppsResource extends Resource {
187
+ /**
188
+ * Search apps by filtering the full catalog
189
+ */
190
+ search: (filterOptions: AppFilterOptions, paginationOptions?: Partial<PaginationOptions>) => Promise<{
191
+ data: SearchableApp[];
192
+ }>;
193
+ /**
194
+ * Get popular apps with preview screens
195
+ */
196
+ popular: (platform?: Platform, limitPerCategory?: number) => Promise<PopularApp[]>;
197
+ /**
198
+ * Get searchable apps list (full catalog for a platform)
199
+ */
200
+ list: (platform?: Platform) => Promise<SearchableApp[]>;
201
+ }
202
+
203
+ declare class AuthResource extends Resource {
204
+ /**
205
+ * Check the auth method for an email (password or magic link)
206
+ */
207
+ checkEmail: (email: string) => Promise<"password" | "magic_link">;
208
+ /**
209
+ * Login with email and password via Supabase Auth directly
210
+ */
211
+ login: (email: string, password: string) => Promise<SupabaseSession>;
212
+ /**
213
+ * Refresh the session using a refresh token
214
+ */
215
+ refresh: (refreshToken: string) => Promise<SupabaseSession>;
216
+ /**
217
+ * Get current user info
218
+ */
219
+ getUser: () => Promise<{
220
+ [key: string]: unknown;
221
+ id: string;
222
+ email: string;
223
+ }>;
224
+ }
225
+
226
+ declare class CollectionsResource extends Resource {
227
+ /**
228
+ * Fetch user collections
229
+ */
230
+ list: () => Promise<Collection[]>;
231
+ /**
232
+ * Fetch saved contents (check if items are saved)
233
+ */
234
+ savedContents: (contentType: ContentType, contentIds: string[]) => Promise<unknown[]>;
235
+ }
236
+
237
+ declare class FiltersResource extends Resource {
238
+ /**
239
+ * Fetch all filter categories and their entries
240
+ */
241
+ list: () => Promise<DictionaryCategory[]>;
242
+ }
243
+
244
+ declare class ScreensResource extends Resource {
245
+ /**
246
+ * Search/fetch screens with filters
247
+ */
248
+ search: (filterOptions: ScreenFilterOptions, paginationOptions?: Partial<PaginationOptions>) => Promise<{
249
+ data: Screen[];
250
+ searchRequestId: string;
251
+ }>;
252
+ /**
253
+ * Download a screen image as a Buffer using the CDN src URL
254
+ */
255
+ download: (cdnSrc: string) => Promise<ArrayBuffer>;
256
+ }
257
+
258
+ declare class SearchResource extends Resource {
259
+ /**
260
+ * Get trending apps
261
+ */
262
+ trendingApps: (platform?: Platform) => Promise<TrendingApp[]>;
263
+ /**
264
+ * Get trending filter tags
265
+ */
266
+ trendingFilterTags: (platform?: Platform, experience?: "apps" | "sites") => Promise<TrendingFilterTag[]>;
267
+ /**
268
+ * Get trending text-in-screenshot keywords
269
+ */
270
+ trendingKeywords: (platform?: Platform) => Promise<{
271
+ platformType: string;
272
+ keyword: string;
273
+ order: number;
274
+ }[]>;
275
+ /**
276
+ * Get searchable sites
277
+ */
278
+ searchableSites: () => Promise<SearchableSite[]>;
279
+ /**
280
+ * Get recent searches
281
+ */
282
+ recentSearches: () => Promise<{
283
+ apps: {
284
+ ios: unknown[];
285
+ web: unknown[];
286
+ };
287
+ sites: unknown[];
288
+ }>;
289
+ }
290
+
291
+ interface MobbinClientOptions {
292
+ /** Provide an access token directly */
293
+ accessToken?: string;
294
+ /** Provide email + password for auto-login */
295
+ credentials?: {
296
+ email: string;
297
+ password: string;
298
+ };
299
+ /** Provide a full session (from stored auth) */
300
+ session?: SupabaseSession;
301
+ }
302
+ declare class MobbinClient {
303
+ private readonly options;
304
+ private _accessToken?;
305
+ private _session?;
306
+ private _fetch;
307
+ private _auth?;
308
+ private _screens?;
309
+ private _apps?;
310
+ private _search?;
311
+ private _filters?;
312
+ private _collections?;
313
+ constructor(options?: MobbinClientOptions);
314
+ /**
315
+ * Initialize the client — login if credentials provided
316
+ */
317
+ init(): Promise<void>;
318
+ get session(): SupabaseSession | undefined;
319
+ set session(session: SupabaseSession);
320
+ get accessToken(): string | undefined;
321
+ set accessToken(token: string);
322
+ get auth(): AuthResource;
323
+ get screens(): ScreensResource;
324
+ get apps(): AppsResource;
325
+ get search(): SearchResource;
326
+ get filters(): FiltersResource;
327
+ get collections(): CollectionsResource;
328
+ }
329
+
330
+ export { type App, type AppFilterOptions, type Collection, type CollectionsResponse, type ContentType, type DictionaryCategory, type DictionaryEntry, type DictionaryResponse, type FetchScreensResponse, MobbinClient, type MobbinClientOptions, type PaginationOptions, type Platform, type PopularApp, type PopularAppsResponse, type SavedContentsResponse, type Screen, type ScreenCdnImgSource, type ScreenFilterOptions, type ScreenMetadata, type SearchAppsResponse, type SearchableApp, type SearchableSite, type SearchableSitesResponse, type SortBy, type SupabaseSession, type TrendingApp, type TrendingAppsResponse, type TrendingFilterTag, type TrendingFilterTagsResponse };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ var T="https://mobbin.com",p="https://ujasntkfphywizsdaapi.supabase.co",u="sb_publishable_LbI2-4spKrYx1xHKrI4YyQ_rC-csyUz",P="sb-ujasntkfphywizsdaapi-auth-token";var k=n=>{let e=encodeURIComponent(JSON.stringify(n)).replaceAll("%2F","/"),s=[];for(let t=0;t<e.length;t+=3180)s.push(e.slice(t,t+3180));return s.map((t,o)=>`${P}.${o}=${t}`).join("; ")},w=n=>({request:async(s,t)=>{let o=t?.baseUrl??T,c=s.startsWith("http")?s:`${o}${s}`,a=new Headers(t?.headers);!a.has("Content-Type")&&t?.method!=="GET"&&a.set("Content-Type","application/json");let h=n.getAccessToken();if(h)if(o===p)a.set("Authorization",`Bearer ${h}`),a.set("apikey",u),a.set("x-client-info","supabase-ssr/0.0.10");else {let l=n.getSession();l&&a.set("Cookie",k(l));}let i=await fetch(c,{...t,headers:a});if(!i.ok){let l=await i.text().catch(()=>"");throw new _(i.status,i.statusText,l,c)}return i.json()}}),_=class extends Error{constructor(s,t,o,c){super(`Mobbin API error ${s} ${t}: ${o} (${c})`);this.status=s;this.statusText=t;this.body=o;this.url=c;this.name="MobbinApiError";}};var r=class{constructor(e){this.fetch=e;}};var f=class extends r{search=async(e,s={})=>{let t=await this.list(e.platform),o=t;if(e.appCategories?.length){let a=await this.popular(e.platform),h=new Set(a.filter(i=>e.appCategories?.includes(i.app_category)).map(i=>i.app_id));o=t.filter(i=>h.has(i.id));}let c=s.pageSize??20;return {data:o.slice(0,c)}};popular=async(e="ios",s=10)=>(await this.fetch.request("/api/popular-apps/fetch-popular-apps-with-preview-screens",{method:"POST",body:JSON.stringify({platform:e,limitPerCategory:s})})).value;list=async(e="ios")=>this.fetch.request(`/api/searchable-apps/${e}`,{method:"GET"})};var d=class extends r{checkEmail=async e=>(await this.fetch.request("/api/auth/login-by-email-only",{method:"POST",body:JSON.stringify({email:e,redirectTo:"/",isForgotPassword:false})})).value.type;login=async(e,s)=>{let t=await fetch(`${p}/auth/v1/token?grant_type=password`,{method:"POST",headers:{"Content-Type":"application/json",apikey:u},body:JSON.stringify({email:e,password:s})});if(!t.ok){let o=await t.text().catch(()=>"");throw new Error(`Login failed (${t.status}): ${o}`)}return t.json()};refresh=async e=>{let s=await fetch(`${p}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{"Content-Type":"application/json",apikey:u},body:JSON.stringify({refresh_token:e})});if(!s.ok){let t=await s.text().catch(()=>"");throw new Error(`Token refresh failed (${s.status}): ${t}`)}return s.json()};getUser=async()=>this.fetch.request(`${p}/auth/v1/user`,{method:"GET",baseUrl:p})};var m=class extends r{list=async()=>(await this.fetch.request("/api/collection/fetch-collections",{method:"POST"})).value;savedContents=async(e,s)=>(await this.fetch.request("/api/saved/fetch-saved-contents",{method:"POST",body:JSON.stringify({contentType:e,contentIds:s})})).value};var y=class extends r{list=async()=>(await this.fetch.request("/api/filter-tags/fetch-dictionary-definitions",{method:"POST"})).value};var g=class extends r{search=async(e,s={})=>{let t={pageSize:s.pageSize??24,sortBy:s.sortBy??"trending",...s};return (await this.fetch.request("/api/content/fetch-screens",{method:"POST",body:JSON.stringify({searchRequestId:"",filterOptions:{platform:e.platform,screenPatterns:e.screenPatterns??null,screenElements:e.screenElements??null,screenKeywords:e.screenKeywords??null,appCategories:e.appCategories??null,hasAnimation:e.hasAnimation??null},paginationOptions:t})})).value};download=async e=>{let s=await fetch(e);if(!s.ok)throw new Error(`Failed to download screen: ${s.status} ${s.statusText}`);return s.arrayBuffer()}};var S=class extends r{trendingApps=async(e="ios")=>(await this.fetch.request("/api/search-bar/fetch-trending-apps",{method:"POST",body:JSON.stringify({platform:e})})).value;trendingFilterTags=async(e="ios",s="apps")=>(await this.fetch.request("/api/search-bar/fetch-trending-filter-tags",{method:"POST",body:JSON.stringify({experience:s,platform:e})})).value;trendingKeywords=async(e="ios")=>(await this.fetch.request("/api/search-bar/fetch-trending-text-in-screenshot-keywords",{method:"POST",body:JSON.stringify({platform:e})})).value;searchableSites=async()=>(await this.fetch.request("/api/search-bar/fetch-searchable-sites",{method:"POST"})).value;recentSearches=async()=>this.fetch.request("/api/recent-searches",{method:"GET"})};var b=class{constructor(e={}){this.options=e;this._accessToken=e.accessToken,this._session=e.session,this._fetch=w({getAccessToken:()=>this._accessToken,getSession:()=>this._session});}_accessToken;_session;_fetch;_auth;_screens;_apps;_search;_filters;_collections;async init(){if(!this._accessToken){if(this._session){this._accessToken=this._session.access_token;return}if(this.options.credentials){let e=await this.auth.login(this.options.credentials.email,this.options.credentials.password);this._session=e,this._accessToken=e.access_token;return}}}get session(){return this._session}set session(e){this._session=e,this._accessToken=e.access_token;}get accessToken(){return this._accessToken}set accessToken(e){this._accessToken=e;}get auth(){return this._auth||(this._auth=new d(this._fetch)),this._auth}get screens(){return this._screens||(this._screens=new g(this._fetch)),this._screens}get apps(){return this._apps||(this._apps=new f(this._fetch)),this._apps}get search(){return this._search||(this._search=new S(this._fetch)),this._search}get filters(){return this._filters||(this._filters=new y(this._fetch)),this._filters}get collections(){return this._collections||(this._collections=new m(this._fetch)),this._collections}};
2
+ export{b as MobbinClient};//# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fetch.ts","../src/resources/base.ts","../src/resources/apps.ts","../src/resources/auth.ts","../src/resources/collections.ts","../src/resources/filters.ts","../src/resources/screens.ts","../src/resources/search.ts","../src/client.ts"],"names":["BASE_URL","SUPABASE_URL","SUPABASE_ANON_KEY","COOKIE_NAME","buildSupabaseCookies","session","encoded","chunks","i","chunk","createFetch","options","path","init","baseUrl","url","headers","token","response","body","MobbinApiError","status","statusText","Resource","fetch","AppsResource","filterOptions","paginationOptions","allApps","filtered","popular","categoryAppIds","a","limit","platform","limitPerCategory","AuthResource","email","password","refreshToken","CollectionsResource","contentType","contentIds","FiltersResource","ScreensResource","pagination","cdnSrc","SearchResource","experience","MobbinClient"],"mappings":"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","file":"index.js","sourcesContent":["const BASE_URL = \"https://mobbin.com\";\nconst SUPABASE_URL = \"https://ujasntkfphywizsdaapi.supabase.co\";\nconst SUPABASE_ANON_KEY = \"sb_publishable_LbI2-4spKrYx1xHKrI4YyQ_rC-csyUz\";\n\nexport interface FetchOptions {\n\tgetAccessToken: () => string | undefined;\n\tgetSession: () => Record<string, unknown> | undefined;\n}\n\nconst COOKIE_NAME = \"sb-ujasntkfphywizsdaapi-auth-token\";\nconst CHUNK_SIZE = 3180;\n\nconst buildSupabaseCookies = (session: Record<string, unknown>): string => {\n\t// Supabase SSR doesn't encode forward slashes in cookies\n\tconst encoded = encodeURIComponent(JSON.stringify(session)).replaceAll(\"%2F\", \"/\");\n\tconst chunks: string[] = [];\n\tfor (let i = 0; i < encoded.length; i += CHUNK_SIZE) {\n\t\tchunks.push(encoded.slice(i, i + CHUNK_SIZE));\n\t}\n\treturn chunks.map((chunk, i) => `${COOKIE_NAME}.${i}=${chunk}`).join(\"; \");\n};\n\nexport const createFetch = (options: FetchOptions) => {\n\tconst request = async <T>(\n\t\tpath: string,\n\t\tinit?: RequestInit & { baseUrl?: string },\n\t): Promise<T> => {\n\t\tconst baseUrl = init?.baseUrl ?? BASE_URL;\n\t\tconst url = path.startsWith(\"http\") ? path : `${baseUrl}${path}`;\n\n\t\tconst headers = new Headers(init?.headers);\n\n\t\tif (!headers.has(\"Content-Type\") && init?.method !== \"GET\") {\n\t\t\theaders.set(\"Content-Type\", \"application/json\");\n\t\t}\n\n\t\tconst token = options.getAccessToken();\n\t\tif (token) {\n\t\t\tif (baseUrl === SUPABASE_URL) {\n\t\t\t\theaders.set(\"Authorization\", `Bearer ${token}`);\n\t\t\t\theaders.set(\"apikey\", SUPABASE_ANON_KEY);\n\t\t\t\theaders.set(\"x-client-info\", \"supabase-ssr/0.0.10\");\n\t\t\t} else {\n\t\t\t\t// Mobbin API routes use Supabase session cookies for auth\n\t\t\t\tconst session = options.getSession();\n\t\t\t\tif (session) {\n\t\t\t\t\theaders.set(\"Cookie\", buildSupabaseCookies(session));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst response = await fetch(url, {\n\t\t\t...init,\n\t\t\theaders,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\tthrow new MobbinApiError(response.status, response.statusText, body, url);\n\t\t}\n\n\t\treturn response.json() as Promise<T>;\n\t};\n\n\treturn { request };\n};\n\nexport class MobbinApiError extends Error {\n\tconstructor(\n\t\tpublic readonly status: number,\n\t\tpublic readonly statusText: string,\n\t\tpublic readonly body: string,\n\t\tpublic readonly url: string,\n\t) {\n\t\tsuper(`Mobbin API error ${status} ${statusText}: ${body} (${url})`);\n\t\tthis.name = \"MobbinApiError\";\n\t}\n}\n\nexport { SUPABASE_URL, SUPABASE_ANON_KEY, BASE_URL };\n","import type { createFetch } from \"../fetch.js\";\n\nexport abstract class Resource {\n\tconstructor(protected readonly fetch: ReturnType<typeof createFetch>) {}\n}\n","import type {\n\tAppFilterOptions,\n\tPaginationOptions,\n\tPlatform,\n\tPopularApp,\n\tPopularAppsResponse,\n\tSearchableApp,\n} from \"../types.js\";\nimport { Resource } from \"./base.js\";\n\nexport class AppsResource extends Resource {\n\t/**\n\t * Search apps by filtering the full catalog\n\t */\n\tsearch = async (\n\t\tfilterOptions: AppFilterOptions,\n\t\tpaginationOptions: Partial<PaginationOptions> = {},\n\t): Promise<{ data: SearchableApp[] }> => {\n\t\tconst allApps = await this.list(filterOptions.platform);\n\t\tlet filtered = allApps;\n\n\t\tif (filterOptions.appCategories?.length) {\n\t\t\t// Filter by category using the popular apps endpoint which has categories\n\t\t\tconst popular = await this.popular(filterOptions.platform);\n\t\t\tconst categoryAppIds = new Set(\n\t\t\t\tpopular\n\t\t\t\t\t.filter((a) => filterOptions.appCategories?.includes(a.app_category))\n\t\t\t\t\t.map((a) => a.app_id),\n\t\t\t);\n\t\t\tfiltered = allApps.filter((a) => categoryAppIds.has(a.id));\n\t\t}\n\n\t\tconst limit = paginationOptions.pageSize ?? 20;\n\t\treturn { data: filtered.slice(0, limit) };\n\t};\n\n\t/**\n\t * Get popular apps with preview screens\n\t */\n\tpopular = async (platform: Platform = \"ios\", limitPerCategory = 10): Promise<PopularApp[]> => {\n\t\tconst res = await this.fetch.request<PopularAppsResponse>(\n\t\t\t\"/api/popular-apps/fetch-popular-apps-with-preview-screens\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({ platform, limitPerCategory }),\n\t\t\t},\n\t\t);\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Get searchable apps list (full catalog for a platform)\n\t */\n\tlist = async (platform: Platform = \"ios\"): Promise<SearchableApp[]> => {\n\t\treturn this.fetch.request<SearchableApp[]>(`/api/searchable-apps/${platform}`, {\n\t\t\tmethod: \"GET\",\n\t\t});\n\t};\n}\n","import { SUPABASE_ANON_KEY, SUPABASE_URL } from \"../fetch.js\";\nimport type { SupabaseSession } from \"../types.js\";\nimport { Resource } from \"./base.js\";\n\ninterface LoginByEmailResponse {\n\tvalue: { type: \"password\" | \"magic_link\" };\n}\n\nexport class AuthResource extends Resource {\n\t/**\n\t * Check the auth method for an email (password or magic link)\n\t */\n\tcheckEmail = async (email: string): Promise<\"password\" | \"magic_link\"> => {\n\t\tconst res = await this.fetch.request<LoginByEmailResponse>(\"/api/auth/login-by-email-only\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify({ email, redirectTo: \"/\", isForgotPassword: false }),\n\t\t});\n\t\treturn res.value.type;\n\t};\n\n\t/**\n\t * Login with email and password via Supabase Auth directly\n\t */\n\tlogin = async (email: string, password: string): Promise<SupabaseSession> => {\n\t\tconst response = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=password`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tapikey: SUPABASE_ANON_KEY,\n\t\t\t},\n\t\t\tbody: JSON.stringify({ email, password }),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(`Login failed (${response.status}): ${body}`);\n\t\t}\n\n\t\treturn response.json() as Promise<SupabaseSession>;\n\t};\n\n\t/**\n\t * Refresh the session using a refresh token\n\t */\n\trefresh = async (refreshToken: string): Promise<SupabaseSession> => {\n\t\tconst response = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tapikey: SUPABASE_ANON_KEY,\n\t\t\t},\n\t\t\tbody: JSON.stringify({ refresh_token: refreshToken }),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(`Token refresh failed (${response.status}): ${body}`);\n\t\t}\n\n\t\treturn response.json() as Promise<SupabaseSession>;\n\t};\n\n\t/**\n\t * Get current user info\n\t */\n\tgetUser = async () => {\n\t\treturn this.fetch.request<{ id: string; email: string; [key: string]: unknown }>(\n\t\t\t`${SUPABASE_URL}/auth/v1/user`,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\tbaseUrl: SUPABASE_URL,\n\t\t\t},\n\t\t);\n\t};\n}\n","import type {\n\tCollection,\n\tCollectionsResponse,\n\tContentType,\n\tSavedContentsResponse,\n} from \"../types.js\";\nimport { Resource } from \"./base.js\";\n\nexport class CollectionsResource extends Resource {\n\t/**\n\t * Fetch user collections\n\t */\n\tlist = async (): Promise<Collection[]> => {\n\t\tconst res = await this.fetch.request<CollectionsResponse>(\"/api/collection/fetch-collections\", {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Fetch saved contents (check if items are saved)\n\t */\n\tsavedContents = async (contentType: ContentType, contentIds: string[]): Promise<unknown[]> => {\n\t\tconst res = await this.fetch.request<SavedContentsResponse>(\"/api/saved/fetch-saved-contents\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify({ contentType, contentIds }),\n\t\t});\n\t\treturn res.value;\n\t};\n}\n","import type { DictionaryCategory, DictionaryResponse } from \"../types.js\";\nimport { Resource } from \"./base.js\";\n\nexport class FiltersResource extends Resource {\n\t/**\n\t * Fetch all filter categories and their entries\n\t */\n\tlist = async (): Promise<DictionaryCategory[]> => {\n\t\tconst res = await this.fetch.request<DictionaryResponse>(\n\t\t\t\"/api/filter-tags/fetch-dictionary-definitions\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t},\n\t\t);\n\t\treturn res.value;\n\t};\n}\n","import type {\n\tFetchScreensResponse,\n\tPaginationOptions,\n\tScreen,\n\tScreenFilterOptions,\n} from \"../types.js\";\nimport { Resource } from \"./base.js\";\n\nexport class ScreensResource extends Resource {\n\t/**\n\t * Search/fetch screens with filters\n\t */\n\tsearch = async (\n\t\tfilterOptions: ScreenFilterOptions,\n\t\tpaginationOptions: Partial<PaginationOptions> = {},\n\t): Promise<{ data: Screen[]; searchRequestId: string }> => {\n\t\tconst pagination: PaginationOptions = {\n\t\t\tpageSize: paginationOptions.pageSize ?? 24,\n\t\t\tsortBy: paginationOptions.sortBy ?? \"trending\",\n\t\t\t...paginationOptions,\n\t\t};\n\n\t\tconst res = await this.fetch.request<FetchScreensResponse>(\"/api/content/fetch-screens\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify({\n\t\t\t\tsearchRequestId: \"\",\n\t\t\t\tfilterOptions: {\n\t\t\t\t\tplatform: filterOptions.platform,\n\t\t\t\t\tscreenPatterns: filterOptions.screenPatterns ?? null,\n\t\t\t\t\tscreenElements: filterOptions.screenElements ?? null,\n\t\t\t\t\tscreenKeywords: filterOptions.screenKeywords ?? null,\n\t\t\t\t\tappCategories: filterOptions.appCategories ?? null,\n\t\t\t\t\thasAnimation: filterOptions.hasAnimation ?? null,\n\t\t\t\t},\n\t\t\t\tpaginationOptions: pagination,\n\t\t\t}),\n\t\t});\n\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Download a screen image as a Buffer using the CDN src URL\n\t */\n\tdownload = async (cdnSrc: string): Promise<ArrayBuffer> => {\n\t\tconst response = await fetch(cdnSrc);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download screen: ${response.status} ${response.statusText}`);\n\t\t}\n\t\treturn response.arrayBuffer();\n\t};\n}\n","import type {\n\tPlatform,\n\tSearchableSite,\n\tSearchableSitesResponse,\n\tTrendingApp,\n\tTrendingAppsResponse,\n\tTrendingFilterTag,\n\tTrendingFilterTagsResponse,\n} from \"../types.js\";\nimport { Resource } from \"./base.js\";\n\nexport class SearchResource extends Resource {\n\t/**\n\t * Get trending apps\n\t */\n\ttrendingApps = async (platform: Platform = \"ios\"): Promise<TrendingApp[]> => {\n\t\tconst res = await this.fetch.request<TrendingAppsResponse>(\n\t\t\t\"/api/search-bar/fetch-trending-apps\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({ platform }),\n\t\t\t},\n\t\t);\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Get trending filter tags\n\t */\n\ttrendingFilterTags = async (\n\t\tplatform: Platform = \"ios\",\n\t\texperience: \"apps\" | \"sites\" = \"apps\",\n\t): Promise<TrendingFilterTag[]> => {\n\t\tconst res = await this.fetch.request<TrendingFilterTagsResponse>(\n\t\t\t\"/api/search-bar/fetch-trending-filter-tags\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({ experience, platform }),\n\t\t\t},\n\t\t);\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Get trending text-in-screenshot keywords\n\t */\n\ttrendingKeywords = async (\n\t\tplatform: Platform = \"ios\",\n\t): Promise<{ platformType: string; keyword: string; order: number }[]> => {\n\t\tconst res = await this.fetch.request<{\n\t\t\tvalue: { platformType: string; keyword: string; order: number }[];\n\t\t}>(\"/api/search-bar/fetch-trending-text-in-screenshot-keywords\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify({ platform }),\n\t\t});\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Get searchable sites\n\t */\n\tsearchableSites = async (): Promise<SearchableSite[]> => {\n\t\tconst res = await this.fetch.request<SearchableSitesResponse>(\n\t\t\t\"/api/search-bar/fetch-searchable-sites\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t},\n\t\t);\n\t\treturn res.value;\n\t};\n\n\t/**\n\t * Get recent searches\n\t */\n\trecentSearches = async (): Promise<{\n\t\tapps: { ios: unknown[]; web: unknown[] };\n\t\tsites: unknown[];\n\t}> => {\n\t\treturn this.fetch.request(\"/api/recent-searches\", { method: \"GET\" });\n\t};\n}\n","import { createFetch } from \"./fetch.js\";\nimport { AppsResource } from \"./resources/apps.js\";\nimport { AuthResource } from \"./resources/auth.js\";\nimport { CollectionsResource } from \"./resources/collections.js\";\nimport { FiltersResource } from \"./resources/filters.js\";\nimport { ScreensResource } from \"./resources/screens.js\";\nimport { SearchResource } from \"./resources/search.js\";\nimport type { SupabaseSession } from \"./types.js\";\n\nexport interface MobbinClientOptions {\n\t/** Provide an access token directly */\n\taccessToken?: string;\n\t/** Provide email + password for auto-login */\n\tcredentials?: {\n\t\temail: string;\n\t\tpassword: string;\n\t};\n\t/** Provide a full session (from stored auth) */\n\tsession?: SupabaseSession;\n}\n\nexport class MobbinClient {\n\tprivate _accessToken?: string;\n\tprivate _session?: SupabaseSession;\n\tprivate _fetch: ReturnType<typeof createFetch>;\n\n\tprivate _auth?: AuthResource;\n\tprivate _screens?: ScreensResource;\n\tprivate _apps?: AppsResource;\n\tprivate _search?: SearchResource;\n\tprivate _filters?: FiltersResource;\n\tprivate _collections?: CollectionsResource;\n\n\tconstructor(private readonly options: MobbinClientOptions = {}) {\n\t\tthis._accessToken = options.accessToken;\n\t\tthis._session = options.session;\n\n\t\tthis._fetch = createFetch({\n\t\t\tgetAccessToken: () => this._accessToken,\n\t\t\tgetSession: () => this._session as unknown as Record<string, unknown> | undefined,\n\t\t});\n\t}\n\n\t/**\n\t * Initialize the client — login if credentials provided\n\t */\n\tasync init(): Promise<void> {\n\t\tif (this._accessToken) return;\n\n\t\tif (this._session) {\n\t\t\tthis._accessToken = this._session.access_token;\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.options.credentials) {\n\t\t\tconst session = await this.auth.login(\n\t\t\t\tthis.options.credentials.email,\n\t\t\t\tthis.options.credentials.password,\n\t\t\t);\n\t\t\tthis._session = session;\n\t\t\tthis._accessToken = session.access_token;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tget session(): SupabaseSession | undefined {\n\t\treturn this._session;\n\t}\n\n\tset session(session: SupabaseSession) {\n\t\tthis._session = session;\n\t\tthis._accessToken = session.access_token;\n\t}\n\n\tget accessToken(): string | undefined {\n\t\treturn this._accessToken;\n\t}\n\n\tset accessToken(token: string) {\n\t\tthis._accessToken = token;\n\t}\n\n\tget auth(): AuthResource {\n\t\tif (!this._auth) this._auth = new AuthResource(this._fetch);\n\t\treturn this._auth;\n\t}\n\n\tget screens(): ScreensResource {\n\t\tif (!this._screens) this._screens = new ScreensResource(this._fetch);\n\t\treturn this._screens;\n\t}\n\n\tget apps(): AppsResource {\n\t\tif (!this._apps) this._apps = new AppsResource(this._fetch);\n\t\treturn this._apps;\n\t}\n\n\tget search(): SearchResource {\n\t\tif (!this._search) this._search = new SearchResource(this._fetch);\n\t\treturn this._search;\n\t}\n\n\tget filters(): FiltersResource {\n\t\tif (!this._filters) this._filters = new FiltersResource(this._fetch);\n\t\treturn this._filters;\n\t}\n\n\tget collections(): CollectionsResource {\n\t\tif (!this._collections) this._collections = new CollectionsResource(this._fetch);\n\t\treturn this._collections;\n\t}\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "mobbin-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial TypeScript SDK for the Mobbin API",
5
+ "author": "yabbal",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "lint": "biome check src/",
26
+ "check": "tsc --noEmit",
27
+ "clean": "rm -rf dist .turbo",
28
+ "prepublishOnly": "pnpm build"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.15.3",
32
+ "tsup": "^8.4.0"
33
+ }
34
+ }