Skip to content

Commit 2da6bde

Browse files
committed
fix: copy demo url
1 parent cabf43a commit 2da6bde

File tree

18 files changed

+594
-327
lines changed

18 files changed

+594
-327
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
"source.fixAll.biome": "explicit",
1616
"source.organizeImports.biome": "explicit"
1717
},
18-
"typescript.experimental.useTsgo": true
18+
"typescript.experimental.useTsgo": false
1919
}

apps/dashboard/app/(main)/websites/[id]/settings/privacy/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function PrivacyPage() {
2323

2424
const isPublic = websiteData?.isPublic ?? false;
2525
const shareableLink = websiteData
26-
? `${window.location.origin}/demo/${websiteId}`
26+
? `${window.location.origin}/websites/${websiteId}`
2727
: '';
2828

2929
const handleTogglePublic = useCallback(async () => {

apps/docs/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import './global.css';
2+
import { Databuddy } from '@databuddy/sdk';
23
import { RootProvider } from 'fumadocs-ui/provider';
34
import type { Metadata, Viewport } from 'next';
45
import { Geist, Geist_Mono, Manrope } from 'next/font/google';
@@ -102,6 +103,7 @@ export default function Layout({ children }: { children: ReactNode }) {
102103
src="https://cdn.databuddy.cc/databuddy.js"
103104
strategy="afterInteractive"
104105
/>
106+
<Databuddy clientId="OXmNQsViBT-FOS_wZCTHc" />
105107
<Head>
106108
<link href="https://icons.duckduckgo.com" rel="preconnect" />
107109
<link href="https://icons.duckduckgo.com" rel="dns-prefetch" />

packages/sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@databuddy/sdk",
3-
"version": "2.1.76",
3+
"version": "2.1.77",
44
"description": "Official Databuddy Analytics SDK",
55
"main": "./dist/core/index.mjs",
66
"types": "./dist/core/index.d.ts",

packages/sdk/src/react/flags/flag-storage.ts renamed to packages/sdk/src/core/flags/browser-storage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
class FlagStorage {
1+
import type { StorageInterface } from './types';
2+
3+
export class BrowserFlagStorage implements StorageInterface {
24
private ttl = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
35

46
get(key: string) {
@@ -131,5 +133,3 @@ class FlagStorage {
131133
}
132134
}
133135
}
134-
135-
export const flagStorage = new FlagStorage();
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
import { logger } from '../../logger';
2+
import type {
3+
FlagResult,
4+
FlagState,
5+
FlagsConfig,
6+
FlagsManager,
7+
FlagsManagerOptions,
8+
StorageInterface,
9+
} from './types';
10+
11+
export class CoreFlagsManager implements FlagsManager {
12+
private config: FlagsConfig;
13+
private storage?: StorageInterface;
14+
private onFlagsUpdate?: (flags: Record<string, FlagResult>) => void;
15+
private onConfigUpdate?: (config: FlagsConfig) => void;
16+
private memoryFlags: Record<string, FlagResult> = {};
17+
private pendingFlags: Set<string> = new Set();
18+
19+
constructor(options: FlagsManagerOptions) {
20+
this.config = this.withDefaults(options.config);
21+
this.storage = options.storage;
22+
this.onFlagsUpdate = options.onFlagsUpdate;
23+
this.onConfigUpdate = options.onConfigUpdate;
24+
25+
logger.setDebug(this.config.debug ?? false);
26+
logger.debug('CoreFlagsManager initialized with config:', {
27+
clientId: this.config.clientId,
28+
debug: this.config.debug,
29+
isPending: this.config.isPending,
30+
hasUser: !!this.config.user,
31+
});
32+
33+
this.initialize();
34+
}
35+
36+
private withDefaults(config: FlagsConfig): FlagsConfig {
37+
return {
38+
clientId: config.clientId,
39+
apiUrl: config.apiUrl ?? 'https://api.databuddy.cc',
40+
user: config.user,
41+
disabled: config.disabled ?? false,
42+
debug: config.debug ?? false,
43+
skipStorage: config.skipStorage ?? false,
44+
isPending: config.isPending,
45+
autoFetch: config.autoFetch !== false,
46+
};
47+
}
48+
49+
private async initialize(): Promise<void> {
50+
if (!this.config.skipStorage && this.storage) {
51+
this.loadCachedFlags();
52+
this.storage.cleanupExpired();
53+
}
54+
55+
if (this.config.autoFetch && !this.config.isPending) {
56+
await this.fetchAllFlags();
57+
}
58+
}
59+
60+
private loadCachedFlags(): void {
61+
if (!this.storage || this.config.skipStorage) {
62+
return;
63+
}
64+
65+
try {
66+
const cachedFlags = this.storage.getAll();
67+
if (Object.keys(cachedFlags).length > 0) {
68+
this.memoryFlags = cachedFlags as Record<string, FlagResult>;
69+
this.notifyFlagsUpdate();
70+
logger.debug('Loaded cached flags:', Object.keys(cachedFlags));
71+
}
72+
} catch (err) {
73+
logger.warn('Error loading cached flags:', err);
74+
}
75+
}
76+
77+
async fetchAllFlags(): Promise<void> {
78+
if (this.config.isPending) {
79+
logger.debug('Session pending, skipping bulk fetch');
80+
return;
81+
}
82+
83+
const params = new URLSearchParams();
84+
params.set('clientId', this.config.clientId);
85+
if (this.config.user?.userId) {
86+
params.set('userId', this.config.user.userId);
87+
}
88+
if (this.config.user?.email) {
89+
params.set('email', this.config.user.email);
90+
}
91+
if (this.config.user?.properties) {
92+
params.set('properties', JSON.stringify(this.config.user.properties));
93+
}
94+
95+
const url = `${this.config.apiUrl}/public/v1/flags/bulk?${params.toString()}`;
96+
97+
try {
98+
const response = await fetch(url);
99+
if (!response.ok) {
100+
throw new Error(`HTTP ${response.status}`);
101+
}
102+
103+
const result = await response.json();
104+
105+
logger.debug('Bulk fetch response:', result);
106+
107+
if (result.flags) {
108+
this.memoryFlags = result.flags;
109+
this.notifyFlagsUpdate();
110+
111+
if (!this.config.skipStorage && this.storage) {
112+
try {
113+
this.storage.setAll(result.flags);
114+
logger.debug('Bulk flags synced to cache');
115+
} catch (err) {
116+
logger.warn('Bulk storage error:', err);
117+
}
118+
}
119+
}
120+
} catch (err) {
121+
logger.error('Bulk fetch error:', err);
122+
}
123+
}
124+
125+
async getFlag(key: string): Promise<FlagResult> {
126+
logger.debug(`Getting: ${key}`);
127+
128+
if (this.config.isPending) {
129+
logger.debug(`Session pending for: ${key}`);
130+
return {
131+
enabled: false,
132+
value: false,
133+
payload: null,
134+
reason: 'SESSION_PENDING',
135+
};
136+
}
137+
138+
if (this.memoryFlags[key]) {
139+
logger.debug(`Memory: ${key}`);
140+
return this.memoryFlags[key];
141+
}
142+
143+
if (this.pendingFlags.has(key)) {
144+
logger.debug(`Pending: ${key}`);
145+
return {
146+
enabled: false,
147+
value: false,
148+
payload: null,
149+
reason: 'FETCHING',
150+
};
151+
}
152+
153+
if (!this.config.skipStorage && this.storage) {
154+
try {
155+
const cached = await this.storage.get(key);
156+
if (cached) {
157+
logger.debug(`Cache: ${key}`);
158+
this.memoryFlags[key] = cached;
159+
this.notifyFlagsUpdate();
160+
return cached;
161+
}
162+
} catch (err) {
163+
logger.warn(`Storage error: ${key}`, err);
164+
}
165+
}
166+
167+
return this.fetchFlag(key);
168+
}
169+
170+
private async fetchFlag(key: string): Promise<FlagResult> {
171+
this.pendingFlags.add(key);
172+
173+
const params = new URLSearchParams();
174+
params.set('key', key);
175+
params.set('clientId', this.config.clientId);
176+
if (this.config.user?.userId) {
177+
params.set('userId', this.config.user.userId);
178+
}
179+
if (this.config.user?.email) {
180+
params.set('email', this.config.user.email);
181+
}
182+
if (this.config.user?.properties) {
183+
params.set('properties', JSON.stringify(this.config.user.properties));
184+
}
185+
186+
const url = `${this.config.apiUrl}/public/v1/flags/evaluate?${params.toString()}`;
187+
188+
logger.debug(`Fetching: ${key}`);
189+
190+
try {
191+
const response = await fetch(url);
192+
if (!response.ok) {
193+
throw new Error(`HTTP ${response.status}`);
194+
}
195+
196+
const result: FlagResult = await response.json();
197+
198+
logger.debug(`Response for ${key}:`, result);
199+
200+
this.memoryFlags[key] = result;
201+
this.notifyFlagsUpdate();
202+
203+
if (!this.config.skipStorage && this.storage) {
204+
try {
205+
this.storage.set(key, result);
206+
logger.debug(`Cached: ${key}`);
207+
} catch (err) {
208+
logger.warn(`Cache error: ${key}`, err);
209+
}
210+
}
211+
212+
return result;
213+
} catch (err) {
214+
logger.error(`Fetch error: ${key}`, err);
215+
216+
const fallback = {
217+
enabled: false,
218+
value: false,
219+
payload: null,
220+
reason: 'ERROR',
221+
};
222+
this.memoryFlags[key] = fallback;
223+
this.notifyFlagsUpdate();
224+
return fallback;
225+
} finally {
226+
this.pendingFlags.delete(key);
227+
}
228+
}
229+
230+
isEnabled(key: string): FlagState {
231+
if (this.memoryFlags[key]) {
232+
return {
233+
enabled: this.memoryFlags[key].enabled,
234+
isLoading: false,
235+
isReady: true,
236+
};
237+
}
238+
if (this.pendingFlags.has(key)) {
239+
return {
240+
enabled: false,
241+
isLoading: true,
242+
isReady: false,
243+
};
244+
}
245+
// Trigger fetch but don't await
246+
this.getFlag(key);
247+
return {
248+
enabled: false,
249+
isLoading: true,
250+
isReady: false,
251+
};
252+
}
253+
254+
refresh(forceClear = false): void {
255+
logger.debug('Refreshing', { forceClear });
256+
257+
if (forceClear) {
258+
this.memoryFlags = {};
259+
this.notifyFlagsUpdate();
260+
if (!this.config.skipStorage && this.storage) {
261+
try {
262+
this.storage.clear();
263+
logger.debug('Storage cleared');
264+
} catch (err) {
265+
logger.warn('Storage clear error:', err);
266+
}
267+
}
268+
}
269+
270+
this.fetchAllFlags();
271+
}
272+
273+
updateUser(user: FlagsConfig['user']): void {
274+
this.config = { ...this.config, user };
275+
this.onConfigUpdate?.(this.config);
276+
this.refresh();
277+
}
278+
279+
updateConfig(config: FlagsConfig): void {
280+
this.config = this.withDefaults(config);
281+
this.onConfigUpdate?.(this.config);
282+
283+
if (!this.config.skipStorage && this.storage) {
284+
this.loadCachedFlags();
285+
this.storage.cleanupExpired();
286+
}
287+
288+
if (this.config.autoFetch && !this.config.isPending) {
289+
this.fetchAllFlags();
290+
}
291+
}
292+
293+
getMemoryFlags(): Record<string, FlagResult> {
294+
return { ...this.memoryFlags };
295+
}
296+
297+
getPendingFlags(): Set<string> {
298+
return new Set(this.pendingFlags);
299+
}
300+
301+
private notifyFlagsUpdate(): void {
302+
this.onFlagsUpdate?.(this.getMemoryFlags());
303+
}
304+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { BrowserFlagStorage } from './browser-storage';
2+
export { CoreFlagsManager } from './flags-manager';
3+
export * from './types';

0 commit comments

Comments
 (0)