Skip to content

Commit 35497a5

Browse files
committed
feat(SDK): allow passing a config URL to setConfig SDK method.
It now throws an error if the config object (or URL) is invalid.
1 parent 341d864 commit 35497a5

11 files changed

Lines changed: 39 additions & 71 deletions

File tree

docs/docs/sdk/js-ts.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ Type: [`(config: Partial<Config>) => Promise<Config>`](../api/interfaces/Playgro
287287

288288
Loads a new project using the passed configuration object.
289289

290+
If it is a string, it is assumed to be a URL to a JSON file that contains the configuration object.
291+
292+
It throws an error if the config object (or URL) is invalid.
293+
290294
```js
291295
import { createPlayground } from 'livecodes';
292296

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"i18n-lokalise-json": "node ./scripts/i18n-lokalise-json.mjs"
7575
},
7676
"dependencies": {
77-
"@live-codes/solid-sdk": "0.4.0",
77+
"@live-codes/solid-sdk": "0.5.0",
7878
"@snackbar/core": "1.7.0",
7979
"codejar": "4.1.1",
8080
"deep-diff": "1.0.2",

src/livecodes/core.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5392,8 +5392,18 @@ const createApi = (): API => {
53925392
return JSON.parse(JSON.stringify(config));
53935393
};
53945394

5395-
const apiSetConfig = async (newConfig: Partial<Config>): Promise<Config> => {
5395+
const apiSetConfig = async (newConfig: Partial<Config> | string): Promise<Config> => {
53965396
const currentConfig = getConfig();
5397+
if (typeof newConfig === 'string') {
5398+
try {
5399+
newConfig = (await fetch(newConfig).then((r) => r.json())) as Partial<Config>;
5400+
} catch {
5401+
return { error: 'Invalid config URL.' } as any;
5402+
}
5403+
}
5404+
if (!newConfig || typeof newConfig !== 'object') {
5405+
return { error: 'Invalid config.' } as any;
5406+
}
53975407
const newAppConfig = buildConfig({ ...currentConfig, ...newConfig });
53985408
const hasNewAppLanguage =
53995409
newConfig.appLanguage && newConfig.appLanguage !== i18n?.getLanguage();

src/sdk/LiveCodes.svelte

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
const configStr = JSON.stringify(currentConfig || '');
6666
const currentGeneration = ++generation;
6767
68-
// avoid race conditions if props change while doing async operations
69-
// (e.g. creating playground or fetching config json)
68+
// avoid race conditions if props change while doing async operation (creating playground)
7069
const isStale = () => currentGeneration !== generation;
7170
7271
if (!playground || otherOptionsCache !== otherOptionsStr) {
@@ -88,15 +87,7 @@
8887
} else {
8988
if (configCache === configStr) return;
9089
configCache = configStr;
91-
92-
if (typeof currentConfig === 'string') {
93-
fetch(currentConfig)
94-
.then((res) => res.json())
95-
.then((json) => {
96-
if (isStale()) return;
97-
playground?.setConfig(json);
98-
});
99-
} else if (currentConfig) {
90+
if (currentConfig) {
10091
playground.setConfig(currentConfig);
10192
}
10293
}

src/sdk/__tests__/use-playground.spec.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ describe('usePlayground – config changes', () => {
294294
expect(createPlaygroundMock).toHaveBeenCalledTimes(2);
295295
});
296296

297-
test('config as URL string fetches JSON and calls setConfig', async () => {
297+
test('config as URL string calls setConfig', async () => {
298298
const hooks = createFakeHooks();
299299
const usePlayground = createUsePlayground(hooks);
300300

@@ -306,21 +306,12 @@ describe('usePlayground – config changes', () => {
306306
hooks.flushEffects();
307307
await flushMicrotasks();
308308

309-
// Second render – config as URL
310-
const fetchedConfig = { title: 'from-url' };
311-
global.fetch = jest.fn().mockResolvedValue({
312-
json: () => Promise.resolve(fetchedConfig),
313-
}) as any;
314-
315309
hooks.resetRefCounter();
316310
usePlayground({ config: 'https://example.com/config.json' });
317311
hooks.flushEffects();
318312
await flushMicrotasks();
319313

320-
expect(global.fetch).toHaveBeenCalledWith('https://example.com/config.json');
321-
expect(mockSetConfig).toHaveBeenCalledWith(fetchedConfig);
322-
323-
(global.fetch as jest.Mock).mockRestore?.();
314+
expect(mockSetConfig).toHaveBeenCalledWith('https://example.com/config.json');
324315
});
325316
});
326317

src/sdk/__tests__/web-components.spec.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -432,25 +432,17 @@ describe('<live-codes> – generation-based staleness', () => {
432432
});
433433

434434
describe('<live-codes> – config URL fetch', () => {
435-
test('config set to URL string fetches JSON and calls setConfig', async () => {
435+
test('config set to URL string calls setConfig', async () => {
436436
const el = document.createElement('live-codes') as any;
437437
document.body.appendChild(el);
438438
await flushMicrotasks();
439439

440440
expect(createPlaygroundMock).toHaveBeenCalledTimes(1);
441441

442-
const fetchedConfig = { title: 'fetched' };
443-
global.fetch = jest.fn().mockResolvedValue({
444-
json: () => Promise.resolve(fetchedConfig),
445-
}) as any;
446-
447442
el.config = 'https://example.com/config.json';
448443
await flushMicrotasks();
449444

450-
expect(global.fetch).toHaveBeenCalledWith('https://example.com/config.json');
451-
expect(mockSetConfig).toHaveBeenCalledWith(fetchedConfig);
452-
453-
(global.fetch as jest.Mock).mockRestore?.();
445+
expect(mockSetConfig).toHaveBeenCalledWith('https://example.com/config.json');
454446
});
455447
});
456448

src/sdk/models.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,10 @@ export interface API {
14551455

14561456
/**
14571457
* Loads a new project using the passed configuration object.
1458+
* If the config is a string, it is assumed to be a URL to a JSON file that contains the configuration object.
1459+
*
1460+
* @throws It throws an error if the config object (or URL) is invalid.
1461+
*
14581462
* @example
14591463
* ```ts
14601464
* import { createPlayground } from "livecodes";
@@ -1471,7 +1475,7 @@ export interface API {
14711475
* });
14721476
* ```
14731477
*/
1474-
setConfig: (config: Partial<Config>) => Promise<Config>;
1478+
setConfig: (config: Partial<Config> | string) => Promise<Config>;
14751479

14761480
/**
14771481
* Gets the playground code (including source code, source language and compiled code) for each editor (markup, style, script), in addition to result page HTML.

src/sdk/use-playground.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ export function createUsePlayground(hooks: Hooks): (props: Props) => PlaygroundH
6969
...otherOptions
7070
} = props;
7171

72-
// avoid race conditions if props change while doing async operations
73-
// (e.g. creating playground or fetching config json)
72+
// avoid race conditions if props change while doing async operation (creating playground)
7473
const generation = ++generationRef.current;
7574
const isStale = () => generationRef.current !== generation || unmountedRef.current;
7675

@@ -96,15 +95,7 @@ export function createUsePlayground(hooks: Hooks): (props: Props) => PlaygroundH
9695
const configStr = JSON.stringify(config);
9796
if (configCacheRef.current === configStr) return;
9897
configCacheRef.current = configStr;
99-
100-
if (typeof config === 'string') {
101-
fetch(config)
102-
.then((res) => res.json())
103-
.then((json) => {
104-
if (isStale()) return;
105-
playgroundRef.current?.setConfig(json);
106-
});
107-
} else if (config) {
98+
if (config) {
10899
playgroundRef.current.setConfig(config);
109100
}
110101
}

src/sdk/vue.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ const LiveCodes: LiveCodesComponent = {
109109
let otherOptionsCache = JSON.stringify(otherOptions);
110110
let generation = 0;
111111

112-
// avoid race conditions if props change while doing async operations
113-
// (e.g. creating playground or fetching config json)
112+
// avoid race conditions if props change while doing async operation (creating playground)
114113
const isStale = (gen: number) => gen !== generation;
115114

116115
onMounted(() => {
@@ -151,12 +150,7 @@ const LiveCodes: LiveCodesComponent = {
151150
});
152151
} else if (JSON.stringify(config) !== configCache) {
153152
configCache = JSON.stringify(config);
154-
155-
if (typeof config === 'string') {
156-
const json = await fetch(config).then((res) => res.json());
157-
if (isStale(currentGeneration)) return;
158-
playground.value?.setConfig(json);
159-
} else if (config) {
153+
if (config) {
160154
playground.value.setConfig((clone(config) as any) || {});
161155
}
162156
}

0 commit comments

Comments
 (0)