Skip to content

Commit

Permalink
initial checkin for timer cleanup (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
seekdavidlee authored Jan 16, 2024
1 parent cc79d5a commit 2773f71
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class LastReadCache {
if (lastReadTime instanceof Date) {
lastReadTime = lastReadTime.toISOString();
}
const data = { chatId, lastReadTime: lastReadTime };
const data = { chatId, lastReadTime };
await this.cache.putValue(chatId, data);
return data;
}
Expand Down
48 changes: 36 additions & 12 deletions packages/mgt-chat/src/statefulClient/Caching/SubscriptionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,33 @@ import { Subscription } from '@microsoft/microsoft-graph-types';
import { isConversationCacheEnabled } from './isConversationCacheEnabled';
import { IDBPObjectStore } from 'idb';

export enum ComponentType {
User = 'user',
Chat = 'chat'
}

type CachedSubscriptionData = CacheItem & {
chatId: string;
componentEntityId: string;
sessionId: string;
subscriptions: Subscription[];
lastAccessDateTime: string;
componentType: ComponentType;
};

const buildCacheKey = (chatId: string, sessionId: string): string => `${chatId}:${sessionId}`;
const buildCacheKey = (componentEntityId: string, sessionId: string): string => `${componentEntityId}:${sessionId}`;

export class SubscriptionsCache {
private get cache(): CacheStore<CachedSubscriptionData> {
const conversation: CacheSchema = schemas.conversation;
return CacheService.getCache<CachedSubscriptionData>(conversation, conversation.stores.subscriptions);
}

public async loadSubscriptions(chatId: string, sessionId: string): Promise<CachedSubscriptionData | undefined> {
public async loadSubscriptions(
componentEntityId: string,
sessionId: string
): Promise<CachedSubscriptionData | undefined> {
if (isConversationCacheEnabled()) {
const cacheKey = buildCacheKey(chatId, sessionId);
const cacheKey = buildCacheKey(componentEntityId, sessionId);
let data;
await this.cache.transaction(async (store: IDBPObjectStore<unknown, [string], string, 'readwrite'>) => {
data = (await store.get(cacheKey)) as CachedSubscriptionData | undefined;
Expand All @@ -41,13 +50,18 @@ export class SubscriptionsCache {
return undefined;
}

public async cacheSubscription(chatId: string, sessionId: string, subscriptionRecord: Subscription): Promise<void> {
public async cacheSubscription(
componentEntityId: string,
componentType: ComponentType,
sessionId: string,
subscriptionRecord: Subscription
): Promise<void> {
await this.cache.transaction(async (store: IDBPObjectStore<unknown, [string], string, 'readwrite'>) => {
log('cacheSubscription', subscriptionRecord);
const cacheKey = buildCacheKey(chatId, sessionId);
const cacheKey = buildCacheKey(componentEntityId, sessionId);

let cacheEntry = (await store.get(cacheKey)) as CachedSubscriptionData | undefined;
if (cacheEntry && cacheEntry.chatId === chatId) {
if (cacheEntry && cacheEntry.componentEntityId === componentEntityId) {
const subIndex = cacheEntry.subscriptions.findIndex(s => s.resource === subscriptionRecord.resource);
if (subIndex !== -1) {
cacheEntry.subscriptions[subIndex] = subscriptionRecord;
Expand All @@ -56,7 +70,8 @@ export class SubscriptionsCache {
}
} else {
cacheEntry = {
chatId,
componentEntityId,
componentType,
sessionId,
subscriptions: [subscriptionRecord],
// we're cheating a bit here to ensure that we have a defined lastAccessDateTime
Expand All @@ -70,11 +85,20 @@ export class SubscriptionsCache {
});
}

public deleteCachedSubscriptions(chatId: string, sessionId: string): Promise<void> {
return this.cache.delete(buildCacheKey(chatId, sessionId));
public deleteCachedSubscriptions(componentEntityId: string, sessionId: string): Promise<void> {
return this.cache.delete(buildCacheKey(componentEntityId, sessionId));
}

public loadInactiveSubscriptions(inactivityThreshold: string): Promise<CachedSubscriptionData[]> {
return this.cache.queryDb('lastAccessDateTime', IDBKeyRange.upperBound(inactivityThreshold));
public loadInactiveSubscriptions(
inactivityThreshold: string,
componentType: ComponentType
): Promise<CachedSubscriptionData[]> {
return this.cache
.queryDb('lastAccessDateTime', IDBKeyRange.upperBound(inactivityThreshold))
.then((data: CachedSubscriptionData[]) => data.filter(d => d.componentType === componentType))
.catch(err => {
// propogate the error back to the call of this function
throw err;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
AadUserConversationMember
} from '@microsoft/microsoft-graph-types';
import { GraphConfig } from './GraphConfig';
import { SubscriptionsCache } from './Caching/SubscriptionCache';
import { SubscriptionsCache, ComponentType } from './Caching/SubscriptionCache';
import { Timer } from '../utils/Timer';

export const appSettings = {
Expand Down Expand Up @@ -177,7 +177,7 @@ export class GraphNotificationClient {
private readonly cacheSubscription = async (subscriptionRecord: Subscription): Promise<void> => {
log(subscriptionRecord);

await this.subscriptionCache.cacheSubscription(this.chatId, this.sessionId, subscriptionRecord);
await this.subscriptionCache.cacheSubscription(this.chatId, ComponentType.Chat, this.sessionId, subscriptionRecord);

// only start timer once. undefined for renewalInterval is semaphore it has stopped.
if (this.renewalTimeout === undefined) this.startRenewalTimer();
Expand Down Expand Up @@ -344,15 +344,15 @@ export class GraphNotificationClient {
appSettings.defaultSubscriptionLifetimeInMinutes * 60 * 1000
);
const threshold = new Date(new Date().getTime() - offset).toISOString();
const inactiveSubs = await this.subscriptionCache.loadInactiveSubscriptions(threshold);
const inactiveSubs = await this.subscriptionCache.loadInactiveSubscriptions(threshold, ComponentType.Chat);
let tasks: Promise<unknown>[] = [];
for (const inactive of inactiveSubs) {
tasks.push(this.removeSubscriptions(inactive.subscriptions));
}
await Promise.all(tasks);
tasks = [];
for (const inactive of inactiveSubs) {
tasks.push(this.subscriptionCache.deleteCachedSubscriptions(inactive.chatId, inactive.sessionId));
tasks.push(this.subscriptionCache.deleteCachedSubscriptions(inactive.componentEntityId, inactive.sessionId));
}
this.startCleanupTimer();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
AadUserConversationMember
} from '@microsoft/microsoft-graph-types';
import { GraphConfig } from './GraphConfig';
import { SubscriptionsCache } from './Caching/SubscriptionCache';
import { SubscriptionsCache, ComponentType } from './Caching/SubscriptionCache';
import { Timer } from '../utils/Timer';

export const appSettings = {
Expand Down Expand Up @@ -170,7 +170,7 @@ export class GraphNotificationUserClient {
private readonly cacheSubscription = async (subscriptionRecord: Subscription): Promise<void> => {
log(subscriptionRecord);

await this.subscriptionCache.cacheSubscription(this.userId, this.sessionId, subscriptionRecord);
await this.subscriptionCache.cacheSubscription(this.userId, ComponentType.User, this.sessionId, subscriptionRecord);

// only start timer once. -1 for renewalInterval is semaphore it has stopped.
if (this.renewalInterval === undefined) this.startRenewalTimer();
Expand Down Expand Up @@ -332,15 +332,15 @@ export class GraphNotificationUserClient {
appSettings.defaultSubscriptionLifetimeInMinutes * 60 * 1000
);
const threshold = new Date(new Date().getTime() - offset).toISOString();
const inactiveSubs = await this.subscriptionCache.loadInactiveSubscriptions(threshold);
const inactiveSubs = await this.subscriptionCache.loadInactiveSubscriptions(threshold, ComponentType.User);
let tasks: Promise<unknown>[] = [];
for (const inactive of inactiveSubs) {
tasks.push(this.removeSubscriptions(inactive.subscriptions));
}
await Promise.all(tasks);
tasks = [];
for (const inactive of inactiveSubs) {
tasks.push(this.subscriptionCache.deleteCachedSubscriptions(inactive.chatId, inactive.sessionId));
tasks.push(this.subscriptionCache.deleteCachedSubscriptions(inactive.componentEntityId, inactive.sessionId));
}
};

Expand Down

0 comments on commit 2773f71

Please sign in to comment.