@ledewire/browser 0.3.0 → 0.5.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 +32 -9
- package/dist/index.d.ts +227 -6
- package/dist/index.js +143 -18
- package/dist/index.js.map +1 -1
- package/dist/ledewire.min.js +1 -1
- package/dist/ledewire.min.js.map +1 -1
- package/llms.txt +290 -0
- package/package.json +3 -2
package/llms.txt
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# @ledewire/browser
|
|
2
|
+
|
|
3
|
+
> LedeWire Browser SDK — buyer authentication, checkout, wallet, and purchases for web apps.
|
|
4
|
+
> Works as a CDN script tag (no build step) or as an ESM npm package.
|
|
5
|
+
> npm install @ledewire/browser
|
|
6
|
+
> Source: https://github.com/ledewire/ledewire-js-sdk
|
|
7
|
+
> API docs: https://ledewire.github.io/ledewire-js-sdk/api/
|
|
8
|
+
|
|
9
|
+
## CDN (no install required)
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<!-- Pin to a major version in production -->
|
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/@ledewire/browser@0/dist/ledewire.min.js"></script>
|
|
14
|
+
<script>
|
|
15
|
+
const lw = Ledewire.init({ apiKey: 'your_api_key' })
|
|
16
|
+
</script>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The global is `Ledewire`. All methods return Promises — use async/await or `.then()`.
|
|
20
|
+
|
|
21
|
+
## npm / ESM install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @ledewire/browser
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { init, localStorageAdapter } from '@ledewire/browser'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## init() configuration
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { init } from '@ledewire/browser'
|
|
35
|
+
import type { BrowserClientConfig } from '@ledewire/browser'
|
|
36
|
+
|
|
37
|
+
const lw = init({
|
|
38
|
+
apiKey: string, // required — your LedeWire platform API key
|
|
39
|
+
|
|
40
|
+
baseUrl?: string, // defaults to 'https://api.ledewire.com'
|
|
41
|
+
|
|
42
|
+
// Token persistence — defaults to MemoryTokenStorage (cleared on page unload)
|
|
43
|
+
storage?: TokenStorage,
|
|
44
|
+
|
|
45
|
+
onTokenRefreshed?: (tokens: StoredTokens) => void,
|
|
46
|
+
onAuthExpired?: () => void, // called when refresh fails — show login UI
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Token refresh is automatic — never call a refresh method manually.
|
|
51
|
+
|
|
52
|
+
## localStorageAdapter
|
|
53
|
+
|
|
54
|
+
Persist sessions across page reloads using `localStorage`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { init, localStorageAdapter } from '@ledewire/browser'
|
|
58
|
+
|
|
59
|
+
const lw = init({
|
|
60
|
+
apiKey: 'your_api_key',
|
|
61
|
+
storage: localStorageAdapter(), // persists under 'lw_tokens' key
|
|
62
|
+
onAuthExpired: () => showLoginModal(),
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use `MemoryTokenStorage` (default) for highest security — tokens are never written
|
|
67
|
+
to disk and are cleared when the page unloads.
|
|
68
|
+
|
|
69
|
+
## Field naming convention
|
|
70
|
+
|
|
71
|
+
All API response types use `snake_case` field names (`access_token`, `price_cents`,
|
|
72
|
+
`content_uri`), matching the wire format exactly.
|
|
73
|
+
|
|
74
|
+
SDK-owned types use `camelCase` where they do not map 1:1 to a raw API response
|
|
75
|
+
field: `StoredTokens.accessToken`, `StoredTokens.expiresAt: number` (a converted
|
|
76
|
+
Unix ms timestamp, not the raw `expires_at: string`).
|
|
77
|
+
|
|
78
|
+
Do not attempt to remap snake_case fields — the SDK has no `transformKeys` option.
|
|
79
|
+
|
|
80
|
+
## Complete method reference
|
|
81
|
+
|
|
82
|
+
### lw.config
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
lw.config.getPublic() → Promise<PublicConfigResponse>
|
|
86
|
+
// { google_client_id: string, ... }
|
|
87
|
+
// Safe to call before authentication — use to get the Google Client ID for sign-in UI.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### lw.auth (buyer and seller auth)
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
lw.auth.signup(body) → Promise<AuthenticationResponse>
|
|
94
|
+
lw.auth.loginWithEmail({ email, password }) → Promise<AuthenticationResponse>
|
|
95
|
+
lw.auth.loginWithGoogle({ id_token }) → Promise<AuthenticationResponse>
|
|
96
|
+
lw.auth.loginWithApiKey({ key, secret? }) → Promise<AuthenticationResponse>
|
|
97
|
+
// key-only → view permission (read-only, sufficient for lw.seller.content.*)
|
|
98
|
+
// key + secret → full permission (read/write)
|
|
99
|
+
lw.auth.logout() → Promise<void>
|
|
100
|
+
lw.auth.requestPasswordReset({ email }) → Promise<AuthPasswordResetResponse>
|
|
101
|
+
lw.auth.resetPassword({ email, reset_code, password }) → Promise<AuthPasswordResetResponse>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Tokens are stored automatically after every successful login.
|
|
105
|
+
|
|
106
|
+
### lw.checkout
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
lw.checkout.state(contentId: string) → Promise<CheckoutStateResponse>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Use this to determine what the current visitor needs to do before accessing content:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const { checkout_state } = await lw.checkout.state('article-id')
|
|
116
|
+
|
|
117
|
+
switch (checkout_state.next_required_action) {
|
|
118
|
+
case 'authenticate':
|
|
119
|
+
// Show login/signup UI
|
|
120
|
+
break
|
|
121
|
+
case 'fund_wallet':
|
|
122
|
+
// Show wallet funding UI (checkout_state.has_sufficient_funds === false)
|
|
123
|
+
break
|
|
124
|
+
case 'purchase':
|
|
125
|
+
// Show purchase confirmation UI
|
|
126
|
+
break
|
|
127
|
+
case 'view_content':
|
|
128
|
+
// User has access — fetch and render the content
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### lw.wallet
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
lw.wallet.balance() → Promise<WalletBalanceResponse>
|
|
137
|
+
// { balance_cents: number }
|
|
138
|
+
|
|
139
|
+
lw.wallet.transactions(params?: PaginationParams) → Promise<{ data: WalletTransactionItem[], pagination: PaginationMeta }>
|
|
140
|
+
|
|
141
|
+
lw.wallet.createPaymentSession({ amount_cents: number }) → Promise<WalletPaymentSessionResponse>
|
|
142
|
+
// { payment_url: string, session_id: string, ... } — redirect user to payment_url
|
|
143
|
+
|
|
144
|
+
lw.wallet.getPaymentStatus(sessionId: string) → Promise<WalletPaymentStatusResponse>
|
|
145
|
+
// Poll after return from payment redirect to confirm funding
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### lw.purchases
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
lw.purchases.create({ content_id: string }) → Promise<PurchaseResponse>
|
|
152
|
+
lw.purchases.list(params?: PaginationParams) → Promise<{ data: PurchaseResponse[], pagination: PaginationMeta }>
|
|
153
|
+
lw.purchases.get(purchaseId: string) → Promise<PurchaseResponse>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### lw.content
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
lw.content.getWithAccess(contentId: string, options?) → Promise<ContentWithAccessResponse>
|
|
160
|
+
// Returns content payload + access info. Requires a valid buyer session.
|
|
161
|
+
// content_type: 'markdown' | 'external_ref'
|
|
162
|
+
// content_body: base64-encoded string (use atob() to decode markdown)
|
|
163
|
+
// content_uri: URL for external_ref content (Vimeo, YouTube, PDF, etc.)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### lw.seller.content (requires API key login)
|
|
167
|
+
|
|
168
|
+
Read-only operations scoped to the authenticated seller's store.
|
|
169
|
+
Store identity is derived server-side from the token — no `store_id` needed.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// 1. Obtain a view token (key-only is sufficient)
|
|
173
|
+
await lw.auth.loginWithApiKey({ key: 'your_api_key' })
|
|
174
|
+
|
|
175
|
+
// 2. List all store content
|
|
176
|
+
lw.seller.content.list() → Promise<ContentResponse[]>
|
|
177
|
+
|
|
178
|
+
// 3. Search by title, URI, or metadata (AND logic — at least one field required)
|
|
179
|
+
lw.seller.content.search({ title?: string, uri?: string, metadata?: Record<string, unknown> })
|
|
180
|
+
→ Promise<ContentResponse[]>
|
|
181
|
+
|
|
182
|
+
// 4. Fetch a single item
|
|
183
|
+
lw.seller.content.get(contentId: string) → Promise<ContentResponse>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Full checkout flow example
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const lw = Ledewire.init({ apiKey: 'your_api_key' })
|
|
190
|
+
|
|
191
|
+
const { checkout_state } = await lw.checkout.state('article-123')
|
|
192
|
+
|
|
193
|
+
switch (checkout_state.next_required_action) {
|
|
194
|
+
case 'authenticate':
|
|
195
|
+
await lw.auth.loginWithEmail({ email, password })
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
case 'fund_wallet':
|
|
199
|
+
const session = await lw.wallet.createPaymentSession({ amount_cents: 500 })
|
|
200
|
+
window.location.href = session.payment_url // Stripe checkout
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
case 'purchase':
|
|
204
|
+
await lw.purchases.create({ content_id: 'article-123' })
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
case 'view_content':
|
|
208
|
+
const { content_type, content_body, content_uri } =
|
|
209
|
+
await lw.content.getWithAccess('article-123')
|
|
210
|
+
if (content_type === 'markdown') {
|
|
211
|
+
renderMarkdown(atob(content_body)) // base64-decode then render
|
|
212
|
+
} else {
|
|
213
|
+
window.location.href = content_uri // redirect to gated video/PDF
|
|
214
|
+
}
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Google OAuth sign-in setup
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// Fetch the platform's Google Client ID before the user signs in:
|
|
223
|
+
const { google_client_id } = await lw.config.getPublic()
|
|
224
|
+
google.accounts.id.initialize({
|
|
225
|
+
client_id: google_client_id,
|
|
226
|
+
callback: async ({ credential }) => {
|
|
227
|
+
await lw.auth.loginWithGoogle({ id_token: credential })
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
google.accounts.id.renderButton(document.getElementById('signin-btn'), { theme: 'outline' })
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Key types
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
interface CheckoutStateResponse {
|
|
237
|
+
content_id: string
|
|
238
|
+
content_title: string
|
|
239
|
+
price_cents: number
|
|
240
|
+
checkout_state: {
|
|
241
|
+
is_authenticated: boolean
|
|
242
|
+
has_sufficient_funds: boolean | null // null when not authenticated
|
|
243
|
+
has_purchased: boolean
|
|
244
|
+
next_required_action: 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface StoredTokens {
|
|
249
|
+
accessToken: string
|
|
250
|
+
refreshToken: string
|
|
251
|
+
expiresAt: number // Unix timestamp (ms)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface TokenStorage {
|
|
255
|
+
getTokens(): Promise<StoredTokens | null>
|
|
256
|
+
setTokens(tokens: StoredTokens): Promise<void>
|
|
257
|
+
clearTokens(): Promise<void>
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Error classes
|
|
262
|
+
|
|
263
|
+
All errors extend `LedewireError`. Import named subclasses for actionable handling:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { LedewireError, AuthError, ForbiddenError, NotFoundError, PurchaseError } from '@ledewire/browser'
|
|
267
|
+
|
|
268
|
+
class LedewireError extends Error {
|
|
269
|
+
statusCode: number // HTTP status code
|
|
270
|
+
code: number | undefined // machine-readable code from API body
|
|
271
|
+
message: string // human-readable description from API body
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
class AuthError extends LedewireError {} // 401 — bad credentials / session expired
|
|
275
|
+
class ForbiddenError extends LedewireError {} // 403 — authenticated but access denied
|
|
276
|
+
class NotFoundError extends LedewireError {} // 404 — resource not found
|
|
277
|
+
class PurchaseError extends LedewireError {} // 409/422 — purchase validation failure
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
try {
|
|
282
|
+
await lw.purchases.create({ content_id: 'article-123' })
|
|
283
|
+
} catch (err) {
|
|
284
|
+
if (err instanceof AuthError) {
|
|
285
|
+
showLoginModal()
|
|
286
|
+
} else if (err instanceof PurchaseError) {
|
|
287
|
+
showError('Purchase failed: ' + err.message)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledewire/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "LedeWire SDK for browsers — embed a paywall with a single script tag",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
-
"README.md"
|
|
18
|
+
"README.md",
|
|
19
|
+
"llms.txt"
|
|
19
20
|
],
|
|
20
21
|
"keywords": [
|
|
21
22
|
"ledewire",
|