Skip to content

Commit

Permalink
configure: sync engine reliability
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Jul 26, 2024
1 parent 186415b commit f0ae40b
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 651 deletions.
30 changes: 9 additions & 21 deletions apps/configure/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* @refresh reload */
import { db, invalidateStore, resetDb } from "./db";
import { type SyncEngine, fetchUserData } from "./sync";
import { initDatabase } from "./sync";

const clientId = "5dd42e00-78e7-474a-954a-bb4e5085e820";

Expand Down Expand Up @@ -46,28 +45,17 @@ export async function verifyOAuthCode(code: string) {
if (!resp.ok) throw new Error("Failed to verify code");
sessionStorage.removeItem("code_verifier");

// TODO: Move this into the sync engine
const data = await resp.json();
await (await db).put("_meta", data.access_token, "accessToken");
await (await db).put("_meta", data.refresh_token, "refreshToken");
invalidateStore("auth");
return data.access_token;
}

export async function fetchAndCacheUserData(accessToken: string) {
const data = await fetchUserData(accessToken);
const result = {
id: data.id,
name: data.displayName,
upn: data.userPrincipalName,
};
localStorage.setItem("user", JSON.stringify(result));
return result;
}
const user = await fetch("https://graph.microsoft.com/v1.0/me", {
headers: {
Authorization: `Bearer ${data.access_token}`,
},
});
if (!user.ok) throw new Error("Failed to fetch user");

export async function logout() {
await (await db).delete("_meta", "accessToken");
invalidateStore("auth");
resetDb();
initDatabase(data.access_token, data.refresh_token, await user.json());
}

const possible =
Expand Down
26 changes: 26 additions & 0 deletions apps/configure/src/lib/createTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type Accessor, onCleanup, onMount, untrack } from "solid-js";

// Run a function on a timer similar to `createTimer` from `@solid-primitives/timer`.
//
// This function is different in:
// - Takes an async callback and delays the timer until the promise is resolved.
// - A manual trigger function is call the callback immediately and reset the timer.
//
export function createTimer2(
fn: () => Promise<any>,
timeout: Accessor<number>,
) {
let timer: number | null = null;
const trigger = () => {
if (timer) clearTimeout(timer);
fn().then(() => {
timer = setTimeout(trigger, untrack(timeout));
});
};

onCleanup(() => {
if (timer) clearTimeout(timer);
});

return { trigger };
}
53 changes: 23 additions & 30 deletions apps/configure/src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,30 @@ export type TableName =
| "scripts"
| "apps";

export type MetaTableKeys =
| `${TableName}|delta`
| `${TableName}|syncedAt`
| "accessToken"
| "refreshToken";

export interface Database extends DBSchema {
// Used to store delta or next page links for each entity
// This allows us to resume fetching data from where we left off or fetch the diff since the last sync.
_meta: {
key: MetaTableKeys;
// Used to store general information
_kv: {
key:
| "user"
| "accessToken"
| "refreshToken"
| "configurationSettings"
| "configurationCategories";
value: string;
};
// Track the nextPage links for each active paginated query and keep track of delta links ready for the next sync.
_meta: {
key: TableName;
value:
| {
nextPage: string;
}
| {
// not all tables we sync support delta links
deltaLink?: string;
syncedAt: Date;
};
};
// Stored views
views: {
key: string;
Expand Down Expand Up @@ -268,6 +279,7 @@ export interface Database extends DBSchema {

export const db = openDB<Database>("data", 1, {
upgrade(db) {
db.createObjectStore("_kv");
db.createObjectStore("_meta");
db.createObjectStore("views", {
keyPath: "id",
Expand Down Expand Up @@ -308,28 +320,9 @@ export const db = openDB<Database>("data", 1, {
},
});

// TODO: Typescript proof this is all the stores
const tables = [
"_meta",
"views",
"users",
"devices",
"groups",
"policies",
"apps",
] as const;

export async function resetDb() {
const tx = (await db).transaction(tables);
for (const table of tables) {
tx.db.clear(table);
}
await tx.done;
}

const syncBroadcastChannel = new BroadcastChannel("sync");

type InvalidationKey = StoreNames<Database> | "auth";
type InvalidationKey = StoreNames<Database> | "auth" | "isSyncing";

// Subscribe to store invalidations to trigger queries to rerun of the data
export function subscribeToInvalidations(
Expand Down
Loading

0 comments on commit f0ae40b

Please sign in to comment.