Commit 9861eaf1 authored by Robert Knight's avatar Robert Knight

Convert `localStorage` service to an ES class

Convert `localStorage` class to an ES class and add types to various
places where it is referenced.

Part of https://github.com/hypothesis/client/issues/3298.
parent 3435f32e
......@@ -104,7 +104,7 @@ import featuresService from './services/features';
import frameSyncService from './services/frame-sync';
import groupsService from './services/groups';
import loadAnnotationsService from './services/load-annotations';
import localStorageService from './services/local-storage';
import { LocalStorageService } from './services/local-storage';
import persistedDefaultsService from './services/persisted-defaults';
import { RouterService } from './services/router';
import serviceUrlService from './services/service-url';
......@@ -142,7 +142,7 @@ function startApp(config, appEl) {
.register('frameSync', frameSyncService)
.register('groups', groupsService)
.register('loadAnnotationsService', loadAnnotationsService)
.register('localStorage', localStorageService)
.register('localStorage', LocalStorageService)
.register('persistedDefaults', persistedDefaultsService)
.register('router', RouterService)
.register('serviceUrl', serviceUrlService)
......
......@@ -23,43 +23,76 @@ class InMemoryStorage {
* A wrapper around the `localStorage` API which provides a fallback to
* in-memory storage in browsers that block access to `window.localStorage`.
* in third-party iframes.
*
* This service also provides convenience methods set and fetch JSON-serializable
* values.
*/
// @inject
export default function localStorage($window) {
let storage;
let testKey = 'hypothesis.testKey';
export class LocalStorageService {
/**
* @param {Window} $window
*/
constructor($window) {
let testKey = 'hypothesis.testKey';
try {
// Test whether we can read/write localStorage.
storage = $window.localStorage;
$window.localStorage.setItem(testKey, testKey);
$window.localStorage.getItem(testKey);
$window.localStorage.removeItem(testKey);
} catch (e) {
storage = new InMemoryStorage();
try {
// Test whether we can read/write localStorage.
this._storage = $window.localStorage;
$window.localStorage.setItem(testKey, testKey);
$window.localStorage.getItem(testKey);
$window.localStorage.removeItem(testKey);
} catch (e) {
this._storage = new InMemoryStorage();
}
}
return {
getItem(key) {
return storage.getItem(key);
},
/**
* Look up a value in local storage.
*
* @param {string} key
* @return {string|null}
*/
getItem(key) {
return this._storage.getItem(key);
}
getObject(key) {
const item = storage.getItem(key);
return item ? JSON.parse(item) : null;
},
/**
* Look up and deserialize a value from storage.
*
* @param {string} key
*/
getObject(key) {
const item = this._storage.getItem(key);
return item ? JSON.parse(item) : null;
}
setItem(key, value) {
storage.setItem(key, value);
},
/**
* Set a value in local storage.
*
* @param {string} key
* @param {string} value
*/
setItem(key, value) {
this._storage.setItem(key, value);
}
setObject(key, value) {
const repr = JSON.stringify(value);
storage.setItem(key, repr);
},
/**
* Serialize `value` to JSON and persist it.
*
* @param {string} key
* @param {any} value
*/
setObject(key, value) {
const repr = JSON.stringify(value);
this._storage.setItem(key, repr);
}
removeItem(key) {
storage.removeItem(key);
},
};
/**
* Remove an item from storage.
*
* @param {string} key
*/
removeItem(key) {
this._storage.removeItem(key);
}
}
......@@ -26,8 +26,9 @@ import { resolve } from '../util/url';
* the `OAuthClient` class.
*
* @param {Window} $window
* @param {import('./toast-messenger').ToastMessengerService} toastMessenger
* @param {import('./api-routes').APIRoutesService} apiRoutes
* @param {import('./local-storage').LocalStorageService} localStorage
* @param {import('./toast-messenger').ToastMessengerService} toastMessenger
* @inject
*/
export default function auth(
......
......@@ -10,6 +10,10 @@ const DEFAULT_KEYS = {
focusedGroup: 'hypothesis.groups.focus',
};
/**
* @param {import('./local-storage').LocalStorageService} localStorage
* @param {import('../store').SidebarStore} store
*/
// @inject
export default function persistedDefaults(localStorage, store) {
/**
......
......@@ -11,6 +11,8 @@
* The `tags` service stores metadata about recently used tags to local storage
* and provides a `filter` method to fetch tags matching a query, ranked based
* on frequency of usage.
*
* @param {import('./local-storage').LocalStorageService} localStorage
*/
// @inject
export default function tags(localStorage) {
......
import service from '../local-storage';
import { LocalStorageService } from '../local-storage';
function windowWithLocalStoragePropertyThatThrows() {
const win = {};
......@@ -22,7 +22,7 @@ function windowWithLocalStorageMethodsThatThrow() {
};
}
describe('sidebar.localStorage', () => {
describe('LocalStorageService', () => {
let fakeWindow;
[
......@@ -34,7 +34,7 @@ describe('sidebar.localStorage', () => {
let key = null;
beforeEach(() => {
localStorage = service($window);
localStorage = new LocalStorageService($window);
key = 'test.memory.key';
});
......@@ -78,7 +78,7 @@ describe('sidebar.localStorage', () => {
});
beforeEach(() => {
localStorage = service(fakeWindow);
localStorage = new LocalStorageService(fakeWindow);
});
it('uses window.localStorage functions to handle data', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment