/** * Browser/React Native-safe marketplace client for platform-service. * * Wraps platform-service /marketplace/* endpoints. * No Node.js dependencies — uses globalThis.fetch. */ import type { CreateListingInput, MarketplaceClient, MarketplaceClientConfig, MarketplaceInstallDoc, MarketplaceListingDoc, MarketplaceReviewDoc, } from './types.js'; function generateRequestId(): string { return typeof globalThis.crypto?.randomUUID === 'function' ? globalThis.crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; } export function createMarketplaceClient(config: MarketplaceClientConfig): MarketplaceClient { const { baseUrl, productId, getAccessToken } = config; function headers(): Record { const h: Record = { 'Content-Type': 'application/json', 'x-product-id': productId, 'x-request-id': generateRequestId(), }; const token = getAccessToken(); if (token) h['Authorization'] = `Bearer ${token}`; return h; } // ── Listings ────────────────────────────────────── async function listListings(query?: { templateType?: string; category?: string; tags?: string; pricingModel?: string; sortBy?: string; q?: string; limit?: number; offset?: number; }): Promise<{ listings: MarketplaceListingDoc[]; total: number }> { const params = new URLSearchParams(); if (query?.templateType) params.set('templateType', query.templateType); if (query?.category) params.set('category', query.category); if (query?.tags) params.set('tags', query.tags); if (query?.pricingModel) params.set('pricingModel', query.pricingModel); if (query?.sortBy) params.set('sortBy', query.sortBy); if (query?.q) params.set('q', query.q); if (query?.limit) params.set('limit', String(query.limit)); if (query?.offset) params.set('offset', String(query.offset)); const qs = params.toString(); const url = qs ? `${baseUrl}/marketplace/listings?${qs}` : `${baseUrl}/marketplace/listings`; const res = await globalThis.fetch(url, { headers: headers() }); if (!res.ok) throw new Error(`listListings failed: ${res.status}`); return (await res.json()) as { listings: MarketplaceListingDoc[]; total: number }; } async function getListing(id: string): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(id)}`, { headers: headers() } ); if (!res.ok) throw new Error(`getListing failed: ${res.status}`); return (await res.json()) as MarketplaceListingDoc; } async function createListing(input: CreateListingInput): Promise { const res = await globalThis.fetch(`${baseUrl}/marketplace/listings`, { method: 'POST', headers: headers(), body: JSON.stringify(input), }); if (!res.ok) throw new Error(`createListing failed: ${res.status}`); return (await res.json()) as MarketplaceListingDoc; } async function updateListing( id: string, updates: Partial ): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(id)}`, { method: 'PATCH', headers: headers(), body: JSON.stringify(updates), } ); if (!res.ok) throw new Error(`updateListing failed: ${res.status}`); return (await res.json()) as MarketplaceListingDoc; } async function submitForCertification( id: string, notes?: string ): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(id)}/submit`, { method: 'POST', headers: headers(), body: JSON.stringify({ notes }), } ); if (!res.ok) throw new Error(`submitForCertification failed: ${res.status}`); return (await res.json()) as MarketplaceListingDoc; } // ── Installs ────────────────────────────────────── async function installListing(listingId: string): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(listingId)}/install`, { method: 'POST', headers: headers(), } ); if (!res.ok) throw new Error(`installListing failed: ${res.status}`); return (await res.json()) as MarketplaceInstallDoc; } async function uninstallListing(listingId: string): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(listingId)}/uninstall`, { method: 'POST', headers: headers(), } ); if (!res.ok) throw new Error(`uninstallListing failed: ${res.status}`); } async function listMyInstalls(query?: { limit?: number; offset?: number; }): Promise { const params = new URLSearchParams(); if (query?.limit) params.set('limit', String(query.limit)); if (query?.offset) params.set('offset', String(query.offset)); const qs = params.toString(); const url = qs ? `${baseUrl}/marketplace/installs?${qs}` : `${baseUrl}/marketplace/installs`; const res = await globalThis.fetch(url, { headers: headers() }); if (!res.ok) throw new Error(`listMyInstalls failed: ${res.status}`); return (await res.json()) as MarketplaceInstallDoc[]; } // ── Reviews ─────────────────────────────────────── async function listReviews( listingId: string, query?: { sortBy?: string; limit?: number } ): Promise { const params = new URLSearchParams(); if (query?.sortBy) params.set('sortBy', query.sortBy); if (query?.limit) params.set('limit', String(query.limit)); const qs = params.toString(); const url = qs ? `${baseUrl}/marketplace/listings/${encodeURIComponent(listingId)}/reviews?${qs}` : `${baseUrl}/marketplace/listings/${encodeURIComponent(listingId)}/reviews`; const res = await globalThis.fetch(url, { headers: headers() }); if (!res.ok) throw new Error(`listReviews failed: ${res.status}`); return (await res.json()) as MarketplaceReviewDoc[]; } async function createReview( listingId: string, input: { rating: number; title: string; body: string } ): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(listingId)}/reviews`, { method: 'POST', headers: headers(), body: JSON.stringify(input), } ); if (!res.ok) throw new Error(`createReview failed: ${res.status}`); return (await res.json()) as MarketplaceReviewDoc; } // ── Reports ─────────────────────────────────────── async function reportListing( listingId: string, input: { reason: string; details: string } ): Promise { const res = await globalThis.fetch( `${baseUrl}/marketplace/listings/${encodeURIComponent(listingId)}/report`, { method: 'POST', headers: headers(), body: JSON.stringify(input), } ); if (!res.ok) throw new Error(`reportListing failed: ${res.status}`); } return { listListings, getListing, createListing, updateListing, submitForCertification, installListing, uninstallListing, listMyInstalls, listReviews, createReview, reportListing, }; }