@autumnsgrove/groveengine 0.4.12 → 0.6.1
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 +5 -3
- package/dist/components/OnboardingChecklist.svelte +118 -0
- package/dist/components/OnboardingChecklist.svelte.d.ts +14 -0
- package/dist/components/quota/QuotaWarning.svelte +125 -0
- package/dist/components/quota/QuotaWarning.svelte.d.ts +16 -0
- package/dist/components/quota/QuotaWidget.svelte +120 -0
- package/dist/components/quota/QuotaWidget.svelte.d.ts +15 -0
- package/dist/components/quota/UpgradePrompt.svelte +288 -0
- package/dist/components/quota/UpgradePrompt.svelte.d.ts +13 -0
- package/dist/components/quota/index.d.ts +8 -0
- package/dist/components/quota/index.js +8 -0
- package/dist/groveauth/client.d.ts +143 -0
- package/dist/groveauth/client.js +502 -0
- package/dist/groveauth/colors.d.ts +35 -0
- package/dist/groveauth/colors.js +91 -0
- package/dist/groveauth/index.d.ts +34 -0
- package/dist/groveauth/index.js +35 -0
- package/dist/groveauth/limits.d.ts +70 -0
- package/dist/groveauth/limits.js +202 -0
- package/dist/groveauth/rate-limit.d.ts +95 -0
- package/dist/groveauth/rate-limit.js +172 -0
- package/dist/groveauth/types.d.ts +139 -0
- package/dist/groveauth/types.js +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/payments/types.d.ts +7 -2
- package/dist/server/services/__mocks__/cloudflare.d.ts +54 -0
- package/dist/server/services/__mocks__/cloudflare.js +470 -0
- package/dist/server/services/cache.d.ts +170 -0
- package/dist/server/services/cache.js +335 -0
- package/dist/server/services/database.d.ts +236 -0
- package/dist/server/services/database.js +450 -0
- package/dist/server/services/index.d.ts +34 -0
- package/dist/server/services/index.js +77 -0
- package/dist/server/services/storage.d.ts +221 -0
- package/dist/server/services/storage.js +485 -0
- package/package.json +11 -1
- package/static/fonts/Calistoga-Regular.ttf +1438 -0
- package/static/fonts/Caveat-Regular.ttf +0 -0
- package/static/fonts/EBGaramond-Regular.ttf +0 -0
- package/static/fonts/Fraunces-Regular.ttf +0 -0
- package/static/fonts/InstrumentSans-Regular.ttf +0 -0
- package/static/fonts/Lora-Regular.ttf +0 -0
- package/static/fonts/Luciole-Regular.ttf +1438 -0
- package/static/fonts/Manrope-Regular.ttf +0 -0
- package/static/fonts/Merriweather-Regular.ttf +1439 -0
- package/static/fonts/Nunito-Regular.ttf +0 -0
- package/static/fonts/PlusJakartaSans-Regular.ttf +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* UpgradePrompt - Modal/dialog for upgrade prompts
|
|
4
|
+
*
|
|
5
|
+
* Shows when user tries to create a post but is at/over limit.
|
|
6
|
+
* Provides options to upgrade, delete posts, or cancel.
|
|
7
|
+
*
|
|
8
|
+
* Accessibility features:
|
|
9
|
+
* - Focus trap within modal
|
|
10
|
+
* - Escape key closes modal
|
|
11
|
+
* - Focus returns to trigger element on close
|
|
12
|
+
* - ARIA attributes for screen readers
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { onMount } from 'svelte';
|
|
16
|
+
import type { SubscriptionStatus } from '../../groveauth/index.js';
|
|
17
|
+
import { TIER_NAMES } from '../../groveauth/index.js';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
open: boolean;
|
|
21
|
+
status: SubscriptionStatus;
|
|
22
|
+
upgradeUrl?: string;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
onProceed?: () => void; // If allowed during grace period
|
|
25
|
+
oldestPostTitle?: string;
|
|
26
|
+
oldestPostDate?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
open,
|
|
31
|
+
status,
|
|
32
|
+
upgradeUrl = '/upgrade',
|
|
33
|
+
onClose,
|
|
34
|
+
onProceed,
|
|
35
|
+
oldestPostTitle,
|
|
36
|
+
oldestPostDate,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
// Reference to the modal dialog element
|
|
40
|
+
let dialogRef: HTMLDivElement | null = $state(null);
|
|
41
|
+
|
|
42
|
+
// Store the previously focused element to restore on close
|
|
43
|
+
let previouslyFocusedElement: HTMLElement | null = null;
|
|
44
|
+
|
|
45
|
+
// Defensive check for malformed status data
|
|
46
|
+
const isValidStatus = $derived(
|
|
47
|
+
status &&
|
|
48
|
+
typeof status === 'object' &&
|
|
49
|
+
typeof status.tier === 'string' &&
|
|
50
|
+
typeof status.post_count === 'number'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Safe status with fallback values
|
|
54
|
+
const safeStatus = $derived(isValidStatus ? status : {
|
|
55
|
+
tier: 'seedling' as const,
|
|
56
|
+
post_count: 0,
|
|
57
|
+
post_limit: 50,
|
|
58
|
+
posts_remaining: 50,
|
|
59
|
+
percentage_used: 0,
|
|
60
|
+
is_at_limit: false,
|
|
61
|
+
is_in_grace_period: false,
|
|
62
|
+
grace_period_days_remaining: null,
|
|
63
|
+
can_create_post: true,
|
|
64
|
+
upgrade_required: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Can proceed if in grace period and not expired
|
|
68
|
+
const canProceed = $derived(
|
|
69
|
+
safeStatus.is_in_grace_period &&
|
|
70
|
+
safeStatus.grace_period_days_remaining !== null &&
|
|
71
|
+
safeStatus.grace_period_days_remaining > 0
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Tier upgrade path
|
|
75
|
+
const nextTier = $derived(
|
|
76
|
+
safeStatus.tier === 'seedling' ? 'sapling' :
|
|
77
|
+
safeStatus.tier === 'sapling' ? 'oak' :
|
|
78
|
+
safeStatus.tier === 'oak' ? 'evergreen' :
|
|
79
|
+
null
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const nextTierName = $derived(nextTier ? TIER_NAMES[nextTier] : null);
|
|
83
|
+
const currentTierName = $derived(TIER_NAMES[safeStatus.tier] || 'Unknown');
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get all focusable elements within the modal
|
|
87
|
+
*/
|
|
88
|
+
function getFocusableElements(): HTMLElement[] {
|
|
89
|
+
if (!dialogRef) return [];
|
|
90
|
+
return Array.from(
|
|
91
|
+
dialogRef.querySelectorAll<HTMLElement>(
|
|
92
|
+
'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Handle keyboard events for focus trap and escape
|
|
99
|
+
*/
|
|
100
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
101
|
+
if (!open) return;
|
|
102
|
+
|
|
103
|
+
if (e.key === 'Escape') {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
onClose();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (e.key === 'Tab') {
|
|
110
|
+
const focusableElements = getFocusableElements();
|
|
111
|
+
if (focusableElements.length === 0) return;
|
|
112
|
+
|
|
113
|
+
const firstElement = focusableElements[0];
|
|
114
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
115
|
+
|
|
116
|
+
if (e.shiftKey) {
|
|
117
|
+
// Shift+Tab: if on first element, go to last
|
|
118
|
+
if (document.activeElement === firstElement) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
lastElement.focus();
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
// Tab: if on last element, go to first
|
|
124
|
+
if (document.activeElement === lastElement) {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
firstElement.focus();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Focus the first focusable element when modal opens
|
|
134
|
+
*/
|
|
135
|
+
function focusFirstElement() {
|
|
136
|
+
const focusableElements = getFocusableElements();
|
|
137
|
+
if (focusableElements.length > 0) {
|
|
138
|
+
focusableElements[0].focus();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle modal open/close
|
|
143
|
+
$effect(() => {
|
|
144
|
+
if (open) {
|
|
145
|
+
// Store the currently focused element
|
|
146
|
+
previouslyFocusedElement = document.activeElement as HTMLElement;
|
|
147
|
+
|
|
148
|
+
// Add escape key listener to document
|
|
149
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
150
|
+
|
|
151
|
+
// Focus the first element after render
|
|
152
|
+
// Use setTimeout to ensure the DOM is ready
|
|
153
|
+
setTimeout(focusFirstElement, 0);
|
|
154
|
+
} else {
|
|
155
|
+
// Remove event listener
|
|
156
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
157
|
+
|
|
158
|
+
// Restore focus to the previously focused element
|
|
159
|
+
if (previouslyFocusedElement) {
|
|
160
|
+
previouslyFocusedElement.focus();
|
|
161
|
+
previouslyFocusedElement = null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Cleanup on unmount
|
|
167
|
+
onMount(() => {
|
|
168
|
+
return () => {
|
|
169
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
{#if open}
|
|
175
|
+
<!-- Backdrop -->
|
|
176
|
+
<div
|
|
177
|
+
class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4"
|
|
178
|
+
onclick={onClose}
|
|
179
|
+
role="presentation"
|
|
180
|
+
>
|
|
181
|
+
<!-- Modal -->
|
|
182
|
+
<div
|
|
183
|
+
bind:this={dialogRef}
|
|
184
|
+
class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl max-w-md w-full p-6"
|
|
185
|
+
onclick={(e) => e.stopPropagation()}
|
|
186
|
+
role="dialog"
|
|
187
|
+
aria-modal="true"
|
|
188
|
+
aria-labelledby="upgrade-title"
|
|
189
|
+
aria-describedby="upgrade-description"
|
|
190
|
+
>
|
|
191
|
+
<!-- Header -->
|
|
192
|
+
<div class="text-center mb-6">
|
|
193
|
+
<div class="mx-auto w-12 h-12 bg-yellow-100 dark:bg-yellow-900/30 rounded-full flex items-center justify-center mb-4">
|
|
194
|
+
<svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
|
195
|
+
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
196
|
+
</svg>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<h3 id="upgrade-title" class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
200
|
+
{#if safeStatus.upgrade_required}
|
|
201
|
+
Upgrade Required
|
|
202
|
+
{:else}
|
|
203
|
+
You're at {safeStatus.post_count}/{safeStatus.post_limit} posts
|
|
204
|
+
{/if}
|
|
205
|
+
</h3>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Content -->
|
|
209
|
+
<div id="upgrade-description" class="space-y-4 mb-6">
|
|
210
|
+
{#if safeStatus.upgrade_required}
|
|
211
|
+
<p class="text-sm text-gray-600 dark:text-gray-400 text-center">
|
|
212
|
+
Your grace period has expired. To continue creating posts, please upgrade your plan or delete some existing posts.
|
|
213
|
+
</p>
|
|
214
|
+
{:else if safeStatus.is_in_grace_period}
|
|
215
|
+
<p class="text-sm text-gray-600 dark:text-gray-400 text-center">
|
|
216
|
+
You're over your post limit. You have <strong class="text-yellow-600 dark:text-yellow-400">{safeStatus.grace_period_days_remaining} days</strong> remaining in your grace period.
|
|
217
|
+
</p>
|
|
218
|
+
|
|
219
|
+
{#if oldestPostTitle}
|
|
220
|
+
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3 text-sm">
|
|
221
|
+
<p class="text-gray-500 dark:text-gray-400">If you continue, new posts may need to replace older ones. Your oldest post:</p>
|
|
222
|
+
<p class="mt-1 font-medium text-gray-900 dark:text-gray-100">"{oldestPostTitle}"</p>
|
|
223
|
+
{#if oldestPostDate}
|
|
224
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">{oldestPostDate}</p>
|
|
225
|
+
{/if}
|
|
226
|
+
</div>
|
|
227
|
+
{/if}
|
|
228
|
+
{:else}
|
|
229
|
+
<p class="text-sm text-gray-600 dark:text-gray-400 text-center">
|
|
230
|
+
You've reached your post limit on the <strong>{currentTierName}</strong> plan. Upgrade to get more posts.
|
|
231
|
+
</p>
|
|
232
|
+
{/if}
|
|
233
|
+
|
|
234
|
+
<!-- Tier comparison -->
|
|
235
|
+
{#if nextTierName}
|
|
236
|
+
<div class="bg-blue-50 dark:bg-blue-900/30 rounded-lg p-4">
|
|
237
|
+
<div class="flex items-center justify-between">
|
|
238
|
+
<div>
|
|
239
|
+
<p class="font-medium text-blue-900 dark:text-blue-100">{nextTierName} Plan</p>
|
|
240
|
+
<p class="text-sm text-blue-700 dark:text-blue-300">
|
|
241
|
+
{#if nextTier === 'sapling'}
|
|
242
|
+
Up to 250 posts
|
|
243
|
+
{:else}
|
|
244
|
+
Unlimited posts
|
|
245
|
+
{/if}
|
|
246
|
+
</p>
|
|
247
|
+
</div>
|
|
248
|
+
<a
|
|
249
|
+
href={upgradeUrl}
|
|
250
|
+
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
|
|
251
|
+
>
|
|
252
|
+
Upgrade
|
|
253
|
+
</a>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
{/if}
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<!-- Actions -->
|
|
260
|
+
<div class="flex flex-col gap-3">
|
|
261
|
+
{#if canProceed && onProceed}
|
|
262
|
+
<button
|
|
263
|
+
onclick={onProceed}
|
|
264
|
+
class="w-full px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
|
|
265
|
+
>
|
|
266
|
+
Continue Anyway
|
|
267
|
+
</button>
|
|
268
|
+
{/if}
|
|
269
|
+
|
|
270
|
+
<div class="flex gap-3">
|
|
271
|
+
<a
|
|
272
|
+
href="/admin/posts"
|
|
273
|
+
class="flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100 text-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
|
|
274
|
+
>
|
|
275
|
+
Manage Posts
|
|
276
|
+
</a>
|
|
277
|
+
|
|
278
|
+
<button
|
|
279
|
+
onclick={onClose}
|
|
280
|
+
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
|
|
281
|
+
>
|
|
282
|
+
Cancel
|
|
283
|
+
</button>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
{/if}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SubscriptionStatus } from '../../groveauth/index.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
open: boolean;
|
|
4
|
+
status: SubscriptionStatus;
|
|
5
|
+
upgradeUrl?: string;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onProceed?: () => void;
|
|
8
|
+
oldestPostTitle?: string;
|
|
9
|
+
oldestPostDate?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const UpgradePrompt: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type UpgradePrompt = ReturnType<typeof UpgradePrompt>;
|
|
13
|
+
export default UpgradePrompt;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Components
|
|
3
|
+
*
|
|
4
|
+
* UI components for displaying and managing post quotas.
|
|
5
|
+
*/
|
|
6
|
+
export { default as QuotaWidget } from './QuotaWidget.svelte';
|
|
7
|
+
export { default as QuotaWarning } from './QuotaWarning.svelte';
|
|
8
|
+
export { default as UpgradePrompt } from './UpgradePrompt.svelte';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Components
|
|
3
|
+
*
|
|
4
|
+
* UI components for displaying and managing post quotas.
|
|
5
|
+
*/
|
|
6
|
+
export { default as QuotaWidget } from './QuotaWidget.svelte';
|
|
7
|
+
export { default as QuotaWarning } from './QuotaWarning.svelte';
|
|
8
|
+
export { default as UpgradePrompt } from './UpgradePrompt.svelte';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GroveAuth Client
|
|
3
|
+
*
|
|
4
|
+
* Client library for integrating with GroveAuth authentication service.
|
|
5
|
+
* Use this to handle OAuth flows, token management, and subscription checks.
|
|
6
|
+
*/
|
|
7
|
+
import type { GroveAuthConfig, TokenResponse, TokenInfo, UserInfo, LoginUrlResult, SubscriptionResponse, CanPostResponse, SubscriptionTier } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Generate a code verifier for PKCE
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateCodeVerifier(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a code challenge from a code verifier using SHA-256
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateCodeChallenge(verifier: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Generate a random state parameter for CSRF protection
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateState(): string;
|
|
20
|
+
export declare class GroveAuthClient {
|
|
21
|
+
private config;
|
|
22
|
+
/**
|
|
23
|
+
* In-memory cache for subscription data.
|
|
24
|
+
* Reduces API calls for frequently accessed subscription info.
|
|
25
|
+
* Cache entries expire after 5 minutes (configurable via cacheTTL).
|
|
26
|
+
*/
|
|
27
|
+
private subscriptionCache;
|
|
28
|
+
private cacheTTL;
|
|
29
|
+
/**
|
|
30
|
+
* Track in-flight subscription requests to prevent duplicate API calls.
|
|
31
|
+
* When multiple concurrent requests come in for the same user,
|
|
32
|
+
* they all wait on the same promise instead of making redundant API calls.
|
|
33
|
+
*/
|
|
34
|
+
private subscriptionPromises;
|
|
35
|
+
constructor(config: GroveAuthConfig & {
|
|
36
|
+
cacheTTL?: number;
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Clear subscription cache for a specific user or all users
|
|
40
|
+
*/
|
|
41
|
+
clearSubscriptionCache(userId?: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Clean up expired cache entries to prevent memory leaks.
|
|
44
|
+
* Call periodically in long-running processes.
|
|
45
|
+
*
|
|
46
|
+
* @returns Number of entries removed
|
|
47
|
+
*/
|
|
48
|
+
cleanupExpiredCache(): number;
|
|
49
|
+
/**
|
|
50
|
+
* Helper for exponential backoff retry logic
|
|
51
|
+
*/
|
|
52
|
+
private withRetry;
|
|
53
|
+
/**
|
|
54
|
+
* Generate a login URL with PKCE
|
|
55
|
+
* Store the state and codeVerifier in a secure cookie for verification
|
|
56
|
+
*/
|
|
57
|
+
getLoginUrl(): Promise<LoginUrlResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Exchange an authorization code for tokens
|
|
60
|
+
*/
|
|
61
|
+
exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse>;
|
|
62
|
+
/**
|
|
63
|
+
* Refresh an access token using a refresh token.
|
|
64
|
+
* Includes automatic retry with exponential backoff for transient failures.
|
|
65
|
+
*
|
|
66
|
+
* @param refreshToken - The refresh token to use
|
|
67
|
+
* @param options.maxRetries - Maximum retry attempts (default: 3)
|
|
68
|
+
* @returns New token response with fresh access token
|
|
69
|
+
* @throws GroveAuthError if refresh fails after all retries
|
|
70
|
+
*/
|
|
71
|
+
refreshToken(refreshToken: string, options?: {
|
|
72
|
+
maxRetries?: number;
|
|
73
|
+
}): Promise<TokenResponse>;
|
|
74
|
+
/**
|
|
75
|
+
* Check if a token is expired or about to expire.
|
|
76
|
+
* Returns true if token expires within the buffer period.
|
|
77
|
+
*
|
|
78
|
+
* @param expiresAt - ISO timestamp of token expiration
|
|
79
|
+
* @param bufferSeconds - Refresh this many seconds before expiry (default: 60)
|
|
80
|
+
*/
|
|
81
|
+
isTokenExpiringSoon(expiresAt: string | Date, bufferSeconds?: number): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Revoke a refresh token
|
|
84
|
+
*/
|
|
85
|
+
revokeToken(refreshToken: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Verify an access token
|
|
88
|
+
*/
|
|
89
|
+
verifyToken(accessToken: string): Promise<TokenInfo | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Get user info using an access token
|
|
92
|
+
*/
|
|
93
|
+
getUserInfo(accessToken: string): Promise<UserInfo>;
|
|
94
|
+
/**
|
|
95
|
+
* Get the current user's subscription
|
|
96
|
+
*/
|
|
97
|
+
getSubscription(accessToken: string): Promise<SubscriptionResponse>;
|
|
98
|
+
/**
|
|
99
|
+
* Get a specific user's subscription (with caching and deduplication)
|
|
100
|
+
*
|
|
101
|
+
* Features:
|
|
102
|
+
* - In-memory caching with configurable TTL
|
|
103
|
+
* - Request deduplication: concurrent requests share the same API call
|
|
104
|
+
* - Automatic cache invalidation on mutations
|
|
105
|
+
*
|
|
106
|
+
* @param accessToken - Valid access token
|
|
107
|
+
* @param userId - User ID to get subscription for
|
|
108
|
+
* @param skipCache - If true, bypasses cache and fetches fresh data
|
|
109
|
+
*/
|
|
110
|
+
getUserSubscription(accessToken: string, userId: string, skipCache?: boolean): Promise<SubscriptionResponse>;
|
|
111
|
+
/**
|
|
112
|
+
* Internal method to fetch user subscription from API
|
|
113
|
+
*/
|
|
114
|
+
private _fetchUserSubscription;
|
|
115
|
+
/**
|
|
116
|
+
* Check if a user can create a new post
|
|
117
|
+
*/
|
|
118
|
+
canUserCreatePost(accessToken: string, userId: string): Promise<CanPostResponse>;
|
|
119
|
+
/**
|
|
120
|
+
* Increment post count after creating a post
|
|
121
|
+
* Automatically invalidates subscription cache for this user
|
|
122
|
+
*/
|
|
123
|
+
incrementPostCount(accessToken: string, userId: string): Promise<SubscriptionResponse>;
|
|
124
|
+
/**
|
|
125
|
+
* Decrement post count after deleting a post
|
|
126
|
+
* Automatically updates subscription cache for this user
|
|
127
|
+
*/
|
|
128
|
+
decrementPostCount(accessToken: string, userId: string): Promise<SubscriptionResponse>;
|
|
129
|
+
/**
|
|
130
|
+
* Update post count to a specific value
|
|
131
|
+
* Automatically updates subscription cache for this user
|
|
132
|
+
*/
|
|
133
|
+
setPostCount(accessToken: string, userId: string, count: number): Promise<SubscriptionResponse>;
|
|
134
|
+
/**
|
|
135
|
+
* Update a user's subscription tier
|
|
136
|
+
* Automatically updates subscription cache for this user
|
|
137
|
+
*/
|
|
138
|
+
updateTier(accessToken: string, userId: string, tier: SubscriptionTier): Promise<SubscriptionResponse>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create a GroveAuth client instance
|
|
142
|
+
*/
|
|
143
|
+
export declare function createGroveAuthClient(config: GroveAuthConfig): GroveAuthClient;
|