Skip to content

Commit

Permalink
feat: add startWith option to createUserStore
Browse files Browse the repository at this point in the history
  • Loading branch information
jacob-8 committed Mar 19, 2024
1 parent 1ab251b commit 6f114e1
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 11 deletions.
12 changes: 8 additions & 4 deletions src/lib/auth/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { getAuth, onAuthStateChanged, signOut, type User } from 'firebase/auth';
import { doc, getFirestore, serverTimestamp, updateDoc } from 'firebase/firestore';

import { getFirebaseApp } from '../init';
import { docStore } from '../firestore/stores';
import { docStore } from '../firestore/stores/doc-store';
import type { IBaseUser } from '../interfaces';
import { setCookie } from '../helpers/cookies';
import { firebaseConfig } from '../config';

export const authState = writable<User>(undefined, (set) => {
if (typeof window !== 'undefined') {
Expand All @@ -20,8 +21,10 @@ export const authState = writable<User>(undefined, (set) => {

/**
* Subscribes to current Firebase user, pulls their data from the users collection, caches it to local storage as well as sets a cookie to allow for server-side rendering (not authenticated routes, just basic UI stuff like a name in a header). It also denotes their visit as a `lastVisit` timestamp in Firestore. */
export function createUserStore<T>({ userKey = 'firebase_user', log = false }) {
const { subscribe, set } = writable<T>(null);
export function createUserStore<T>(options: { userKey?: string; log?: boolean; startWith?: T }) {
const { userKey = `${firebaseConfig.projectId}_firebase_user`, log = false, startWith = null } = options;

const { subscribe, set } = writable<T>(startWith);
let unsub: Unsubscriber;

if (typeof window !== 'undefined') {
Expand Down Expand Up @@ -62,8 +65,9 @@ function cacheUser(user: IBaseUser, userKey: string) {
displayName: user.displayName,
email: user.email,
photoURL: user.photoURL || null,
roles: user.roles || null,
}; // Cookies are limited to 4kb, about 1,000-4000 characters
setCookie('user', JSON.stringify(minimalUser), { 'max-age': 31536000 });
setCookie('user', JSON.stringify(minimalUser), { maxAge: 365 * 24 * 60 * 60 });
}

function removeCachedUser(userKey: string) {
Expand Down
31 changes: 24 additions & 7 deletions src/lib/helpers/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,35 @@ export function getCookie(name: string, cookies?) {
return decodeURIComponent(cookieValue.trim());
}

export function setCookie(name: string, value, options: any = {}) {
if (options.expires instanceof Date) {
options.expires = options.expires.toUTCString();
}
export function setCookie(name: string, value: string, options: CookieOptions = {}) {
document.cookie = formatCookie(name, value, options)
}

interface CookieOptions {
domain?: string;
expires?: string | Date;
httpOnly?: boolean;
maxAge?: number;
path?: string;
sameSite?: 'strict' | 'lax' | 'none';
secure?: boolean;
}

function formatCookie(name: string, value: string, options: CookieOptions = {}) {
if (options.expires instanceof Date)
options.expires = options.expires.toUTCString()

const updatedCookie = {
[encodeURIComponent(name)]: encodeURIComponent(value),
sameSite: 'strict',
path: '/',
...options,
};
}

document.cookie = Object.entries(updatedCookie)
const cookie = Object.entries(updatedCookie)
.filter(([key]) => key !== 'secure')
.map((kv) => kv.join('='))
.join(';');
.join(';')

return options.secure === false ? cookie : `${cookie};secure`
}
1 change: 1 addition & 0 deletions src/lib/interfaces/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface User {
providerIds?: SignInMethods[]; // 'emailLink' method will still say 'password'
emailVerified?: boolean;
emailLink?: boolean; // set to true if they are both a new user and email is verified; this is the only way to distinguish apart from users who use email+pass and then later manually verify their email if the app provides that option.
roles?: { admin?: number }; // not from Firebase - used by 2 projects downstream - most users can ignore this
}

export type SignInMethods = 'google.com' | 'password' | 'emailLink' | 'facebook.com' | 'github.com' | 'phone' | 'twitter.com';

0 comments on commit 6f114e1

Please sign in to comment.