Skip to content

Firebase Remote Config #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { getDatabase } from "firebase/database";
import { getAuth } from "firebase/auth";

// Initialize Firebase
const app = initializeApp(/* your firebase config */);
export const app = initializeApp(/* your firebase config */);
export const db = getFirestore(app);
export const rtdb = getDatabase(app);
export const auth = getAuth(app);
Expand Down Expand Up @@ -431,7 +431,65 @@ Upload a file with progress tracking
</UploadTask>
```

### Using Components Together
### RemoteConfig

Instantiate a `RemoteConfig` instance from your initialized `FirebaseApp` and fetch Firebase Remote Config variables. It's possible to configure a `defaultValue` to give a fallback value for non-available variables and a `minimumFetchIntervalMillis` to let RemoteConfig caches the values for a given time. *Be careful not to fetch too often, as your requests might be throttled.*
This components takes care of intializing the RemoteConfig instance from a client side context only, as RemoteConfig is brower-dependant.

```svelte
<script>
import { initializeApp } from 'firebase/app';
import { FirebaseApp, RemoteConfig } from 'sveltefire';
const app = initializeApp(/* your firebase config */);
const testValue = {
greetingText: 'Hello World',
answer: 42,
isActive: true
};
</script>
<FirebaseApp {app}>
<RemoteConfig defaultValue={testValue} let:remoteConfig>
<!-- You can use the built remote config here -->
<!-- This type of configuration is due to RemoteConfig being dependant of the browser -->
</RemoteConfig>
</FirebaseApp>
```

When initialized, you can use the `remoteConfig` store to access your remote configurations by using a `RemoteConfigBoolean`, `RemoteConfigNumber`, `RemoteConfigString` or `RemoteConfigValue`.

### RemoteConfigBoolean, RemoteConfigNumber, RemoteConfigString

Get a typed value from your RemoteConfig instance.

```svelte
<script>
import { initializeApp } from 'firebase/app';
import { FirebaseApp, RemoteConfig, RemoteConfigString, RemoteConfigBoolean, RemoteConfigNumber } from 'sveltefire';
const app = initializeApp(/* your firebase config */);
const testValue = {
greetingText: 'Hello World',
answer: 42,
isActive: true
};
</script>
<FirebaseApp {app}>
<RemoteConfig defaultValue={testValue} let:remoteConfig>
<RemoteConfigString {remoteConfig} variableName="greetingText" let:configValue>
<p>Greeting text: {configValue}</p>
</RemoteConfigString>

<RemoteConfigBoolean {remoteConfig} variableName="isActive" let:configValue>
<p>Is active? {configValue}</p>
</RemoteConfigBoolean>

<RemoteConfigNumber {remoteConfig} variableName="answer" let:configValue>
<p>Answer: {configValue}</p>
</RemoteConfigNumber>
</RemoteConfig>
</FirebaseApp>
```

## Using Components Together

These components can be combined to build complex realtime apps. It's especially powerful when fetching data that requires the current user's UID or a related document's path.

Expand Down Expand Up @@ -487,5 +545,5 @@ These components can be combined to build complex realtime apps. It's especially

- ~~Add support for Firebase Storage~~ (Added in latest release!)
- ~~Add support for Firebase RTDB~~ (Added in latest release!)

- Add support for Firebase Analytics in SvelteKit
- Find a way to make TS generics with with Doc/Collection components
2 changes: 1 addition & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"port": 9199
},
"hosting": {
"port": 5000
"port": 5001
},
"ui": {
"enabled": true
Expand Down
4 changes: 3 additions & 1 deletion src/lib/components/FirebaseApp.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<script lang="ts">
import { setFirebaseContext } from "$lib/stores/sdk.js";
import type { FirebaseApp } from "firebase/app";
import type { Auth } from "firebase/auth";
import type { Firestore } from "firebase/firestore";
import type { Database } from "firebase/database";
import type { FirebaseStorage } from "firebase/storage";

export let app: FirebaseApp;
export let firestore: Firestore;
export let rtdb: Database;
export let auth: Auth;
export let storage: FirebaseStorage;

setFirebaseContext({ firestore, rtdb, auth, storage });
setFirebaseContext({ firestore, rtdb, auth, storage, app });
</script>

<slot />
39 changes: 39 additions & 0 deletions src/lib/components/remote-config/RemoteConfig.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts">
import { getFirebaseContext } from '$lib/stores/sdk.js';
import type { RemoteConfig } from 'firebase/remote-config';
import { getRemoteConfig } from 'firebase/remote-config';
import { remoteConfigActivationStore } from '$lib/stores/remote-config.js';
import { onMount } from 'svelte';

export let defaultValue = {};
export let minimumFetchIntervalMillis = 3600000;

let configActivated;
let remoteConfig: RemoteConfig | undefined;

const { app } = getFirebaseContext();

// Initialize remote config instance with default values and minimum fetch interval
// The instance will be created when the component is mounted because Remote Config
// requires the code to run in the browser
onMount(() => {

remoteConfig = getRemoteConfig(app);
remoteConfig.settings.minimumFetchIntervalMillis = minimumFetchIntervalMillis;
remoteConfig.defaultConfig = defaultValue;

configActivated = remoteConfigActivationStore(remoteConfig);

});

interface $$Slots {
default: { remoteConfig?: RemoteConfig },
loading: {},
}
</script>

{#if remoteConfig !== undefined && $configActivated === true}
<slot {remoteConfig} />
{:else}
<slot name="loading" />
{/if}
21 changes: 21 additions & 0 deletions src/lib/components/remote-config/RemoteConfigBoolean.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import type { RemoteConfig } from "firebase/remote-config";
import { booleanConfigStore } from "$lib/stores/remote-config.js";

export let remoteConfig: RemoteConfig | undefined;
export let variableName: string | undefined;

let store = booleanConfigStore(remoteConfig!, variableName!);

interface $$Slots {
default: { configValue: string | undefined; },
loading: {},
}

</script>

{#if $store !== undefined}
<slot configValue={$store} />
{:else}
<slot name="loading" />
{/if}
23 changes: 23 additions & 0 deletions src/lib/components/remote-config/RemoteConfigNumber.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { numberConfigStore } from "$lib/stores/remote-config.js";
import type { RemoteConfig } from "firebase/remote-config";

export let remoteConfig: RemoteConfig | undefined;
export let variableName: string | undefined;

let store;

store = numberConfigStore(remoteConfig!, variableName!);

interface $$Slots {
default: { configValue: number | undefined; },
loading: {},
}

</script>

{#if $store !== undefined}
<slot configValue={$store} />
{:else}
<slot name="loading" />
{/if}
23 changes: 23 additions & 0 deletions src/lib/components/remote-config/RemoteConfigString.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import type { RemoteConfig } from "firebase/remote-config";
import { stringConfigStore } from "$lib/stores/remote-config.js";

export let remoteConfig: RemoteConfig | undefined;
export let variableName: string | undefined;

let store;

store = stringConfigStore(remoteConfig!, variableName!);

interface $$Slots {
default: { configValue: string | undefined; },
loading: {},
}

</script>

{#if $store !== undefined}
<slot configValue={$store} />
{:else}
<slot name="loading" />
{/if}
21 changes: 21 additions & 0 deletions src/lib/components/remote-config/RemoteConfigValue.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import type { RemoteConfig } from "firebase/remote-config";
import { valueConfigStore } from "$lib/stores/remote-config.js";

export let remoteConfig: RemoteConfig | undefined;
export let variableName: string | undefined;

let store = valueConfigStore(remoteConfig!, variableName!);

interface $$Slots {
default: { configValue: any | undefined; },
loading: {},
}

</script>

{#if $store !== undefined}
<slot configValue={$store} />
{:else}
<slot name="loading" />
{/if}
147 changes: 147 additions & 0 deletions src/lib/stores/remote-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { readable } from "svelte/store";

import { fetchAndActivate, getBoolean, getNumber, getString, getValue, isSupported, type RemoteConfig } from "firebase/remote-config";


/**
* @param {RemoteConfig} remoteConfig firebase remoteConfig instance
* @param {any} defaultValue optional default data.
* @returns a store with the fallback remote config value as an object
*/
function fallbacks(remoteConfig: RemoteConfig, defaultValue: any | undefined = undefined){
// Fallback for SSR
if (!globalThis.window) {
const { subscribe } = readable(defaultValue);
return {
subscribe,
};
}

// Fallback for missing SDK
if (!remoteConfig) {
console.warn(
"Firebase RemoteConfig is not initialized. Are you missing FirebaseApp as a parent component?"
);
const { subscribe } = readable(null);
return {
subscribe,
};
}
}

/**
* @param {RemoteConfig} remoteConfig firebase remoteConfig instance
* @returns a store with the remote config activation status as a boolean
*/
export function remoteConfigActivationStore(remoteConfig: RemoteConfig) {

const fallbackValue = fallbacks(remoteConfig, undefined);

if(fallbackValue){
return fallbackValue;
}

const { subscribe } = readable<boolean | undefined>(undefined, (set) => {
isSupported().then(async (isSupported) => {
if (isSupported) {
fetchAndActivate(remoteConfig).then(() => { set(true) });
}
});
});

return {
subscribe,
};
}

/**
* @param {RemoteConfig} remoteConfig firebase remoteConfig instance
* @param {string} configKey the key of the remote config value
* @param {any} defaultValue optional default data.
* @returns a store with the requested remote config value as an object
*/
export function valueConfigStore(remoteConfig: RemoteConfig, configKey: string, defaultValue: any | undefined = undefined) {

const fallbackValue = fallbacks(remoteConfig, defaultValue);

if(fallbackValue){
return fallbackValue;
}

const { subscribe } = readable(defaultValue, (set) => {
set(getValue(remoteConfig, configKey));
});

return {
subscribe,
};
}

/**
* @param {RemoteConfig} remoteConfig firebase remoteConfig instance
* @param {string} configKey the key of the remote config value
* @param {boolean} defaultValue optional default data.
* @returns a store with the requested remote config value as a boolean
*/
export function booleanConfigStore(remoteConfig: RemoteConfig, configKey: string, defaultValue: boolean | undefined = undefined) {

const fallbackValue = fallbacks(remoteConfig, defaultValue);

if(fallbackValue){
return fallbackValue;
}

const { subscribe } = readable(defaultValue, (set) => {
set(getBoolean(remoteConfig, configKey));
});

return {
subscribe,
};
}

/**
* @param {RemoteConfig} remoteConfig firebase remoteConfig instance
* @param {string} configKey the key of the remote config value
* @param {string | undefined} defaultValue optional default data.
* @returns a store with the requested remote config value as a string
*/
export function stringConfigStore(remoteConfig: RemoteConfig, configKey: string, defaultValue: string | undefined = undefined) {

const fallbackValue = fallbacks(remoteConfig, defaultValue);

if(fallbackValue){
return fallbackValue;
}

const { subscribe } = readable(defaultValue, (set) => {
set(getString(remoteConfig, configKey));
});

return {
subscribe,
};
}

/**
* @param {RemoteConfig} remoteConfig firebase remoteConfig instance
* @param {string} configKey the key of the remote config value
* @param {number | undefined} defaultValue optional default data.
* @returns a store with the requested remote config value as a number
*/
export function numberConfigStore(remoteConfig: RemoteConfig, configKey: string, defaultValue: number | undefined = undefined) {

const fallbackValue = fallbacks(remoteConfig, defaultValue);

if(fallbackValue){
return fallbackValue;
}

const { subscribe } = readable(defaultValue, (set) => {
set(getNumber(remoteConfig, configKey));
});

return {
subscribe,
};
}
Loading