-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bdf69e8
commit 131ee7a
Showing
11 changed files
with
50 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,55 @@ | ||
import { SplitIO } from '../types'; | ||
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types'; | ||
import { setToArray, ISet } from '../utils/lang/sets'; | ||
import { getMatching } from '../utils/key'; | ||
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser'; | ||
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types'; | ||
|
||
/** | ||
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function | ||
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js) | ||
* Factory of client-side storage loader | ||
* | ||
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property. | ||
* @param storage object containing `splits` and `segments` cache (client-side variant) | ||
* @param userKey user key (matching key) of the provided MySegmentsCache | ||
* | ||
* @TODO extend to load largeSegments | ||
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag. | ||
* @TODO add logs, and input validation in this module, in favor of size reduction. | ||
* @TODO unit tests | ||
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader | ||
* and extended with a `mySegmentsData` property. | ||
* @returns function to preload the storage | ||
*/ | ||
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) { | ||
// Do not load data if current preloadedData is empty | ||
if (Object.keys(preloadedData).length === 0) return; | ||
|
||
const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData; | ||
export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader { | ||
|
||
/** | ||
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function | ||
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js) | ||
* | ||
* @param storage object containing `splits` and `segments` cache (client-side variant) | ||
* @param userId user key string of the provided MySegmentsCache | ||
* | ||
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic. | ||
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag. | ||
*/ | ||
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) { | ||
// Do not load data if current preloadedData is empty | ||
if (Object.keys(preloadedData).length === 0) return; | ||
|
||
const { lastUpdated = -1, segmentsData = {}, since = -1, splitsData = {} } = preloadedData; | ||
|
||
if (storage.splits) { | ||
const storedSince = storage.splits.getChangeNumber(); | ||
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS; | ||
|
||
// Do not load data if current data is more recent | ||
if (storedSince > since) return; | ||
// Do not load data if current localStorage data is more recent, | ||
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`, | ||
if (storedSince > since || lastUpdated < expirationTimestamp) return; | ||
|
||
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data | ||
storage.splits.clear(); | ||
storage.splits.setChangeNumber(since); | ||
|
||
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data | ||
storage.splits.addSplits(splitsData.map(split => ([split.name, split]))); | ||
} | ||
storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName]))); | ||
|
||
if (matchingKey) { // add mySegments data (client-side) | ||
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[matchingKey]; | ||
// add mySegments data | ||
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId]; | ||
if (!mySegmentsData) { | ||
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds | ||
mySegmentsData = Object.keys(segmentsData).filter(segmentName => { | ||
const matchingKeys = segmentsData[segmentName]; | ||
return matchingKeys.indexOf(matchingKey) > -1; | ||
const userIds = JSON.parse(segmentsData[segmentName]).added; | ||
return Array.isArray(userIds) && userIds.indexOf(userId) > -1; | ||
}); | ||
} | ||
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) }); | ||
} else { // add segments data (server-side) | ||
Object.keys(segmentsData).filter(segmentName => { | ||
const matchingKeys = segmentsData[segmentName]; | ||
storage.segments.addToSegment(segmentName, matchingKeys); | ||
}); | ||
} | ||
} | ||
|
||
export function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData { | ||
return { | ||
// lastUpdated: Date.now(), | ||
since: storage.splits.getChangeNumber(), | ||
splitsData: storage.splits.getAll(), | ||
segmentsData: userKeys ? | ||
undefined : // @ts-ignore accessing private prop | ||
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop | ||
prev[cur] = setToArray(storage.segments.segmentCache[cur] as ISet<string>); | ||
return prev; | ||
}, {}), | ||
mySegmentsData: userKeys ? | ||
userKeys.reduce<Record<string, string[]>>((prev, userKey) => { | ||
prev[getMatching(userKey)] = storage.shared ? | ||
// Client-side segments | ||
// @ts-ignore accessing private prop | ||
Object.keys(storage.shared(userKey).segments.segmentCache) : | ||
// Server-side segments | ||
// @ts-ignore accessing private prop | ||
Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop | ||
return storage.segments.segmentCache[segmentName].has(userKey) ? | ||
prev.concat(segmentName) : | ||
prev; | ||
}, []); | ||
return prev; | ||
}, {}) : | ||
undefined | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters