mirror of
https://github.com/wassname/talk.git
synced 2026-09-12 13:01:11 +08:00
Full PromisifiedStorage + Simplifications
This commit is contained in:
@@ -24,16 +24,10 @@ export interface TalkContext {
|
||||
timeagoFormatter?: Formatter;
|
||||
|
||||
/** Local Storage */
|
||||
localStorage: Storage;
|
||||
localStorage: PromisifiedStorage;
|
||||
|
||||
/** Session storage */
|
||||
sessionStorage: Storage;
|
||||
|
||||
/** Session Storage over pym */
|
||||
pymLocalStorage?: PromisifiedStorage;
|
||||
|
||||
/** Session storage over pym */
|
||||
pymSessionStorage?: PromisifiedStorage;
|
||||
sessionStorage: PromisifiedStorage;
|
||||
|
||||
/** media query values for testing purposes */
|
||||
mediaQueryValues?: MediaQueryMatchers;
|
||||
|
||||
@@ -128,14 +128,12 @@ export default async function createContext({
|
||||
registerClickFarAway,
|
||||
rest: new RestClient("/api", tokenGetter),
|
||||
postMessage: new PostMessageService(),
|
||||
localStorage: createLocalStorage(),
|
||||
sessionStorage: createSessionStorage(),
|
||||
pymLocalStorage:
|
||||
(pym && (inIframe && createPymStorage(pym, "localStorage"))) ||
|
||||
createPromisifiedStorage(createLocalStorage("talkPym")),
|
||||
pymSessionStorage:
|
||||
(pym && (inIframe && createPymStorage(pym, "sessionStorage"))) ||
|
||||
createPromisifiedStorage(createSessionStorage("talkPym")),
|
||||
localStorage:
|
||||
(pym && inIframe && createPymStorage(pym, "localStorage")) ||
|
||||
createPromisifiedStorage(createLocalStorage()),
|
||||
sessionStorage:
|
||||
(pym && inIframe && createPymStorage(pym, "sessionStorage")) ||
|
||||
createPromisifiedStorage(createSessionStorage()),
|
||||
browserInfo: getBrowserInfo(),
|
||||
};
|
||||
|
||||
|
||||
@@ -16,10 +16,19 @@ it("should return length", () => {
|
||||
expect(storage.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should nth value", () => {
|
||||
it("should nth key", () => {
|
||||
const storage = createInMemoryStorage();
|
||||
storage.setItem("a", "a");
|
||||
storage.setItem("b", "b");
|
||||
storage.setItem("c", "c");
|
||||
storage.setItem("a", "0");
|
||||
storage.setItem("b", "1");
|
||||
storage.setItem("c", "2");
|
||||
expect(storage.key(2)).toBe("c");
|
||||
});
|
||||
|
||||
it("accepts predefined data", () => {
|
||||
const storage = createInMemoryStorage({
|
||||
a: "0",
|
||||
b: "1",
|
||||
c: "2",
|
||||
});
|
||||
expect(storage.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Storage
|
||||
*/
|
||||
class InMemoryStorage implements Storage {
|
||||
private storage: Record<string, string>;
|
||||
private data: Record<string, string>;
|
||||
|
||||
constructor() {
|
||||
this.storage = {};
|
||||
constructor(data: Record<string, string> = {}) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return Object.keys(this.storage).length;
|
||||
return Object.keys(this.data).length;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.storage = {};
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
public key(n: number) {
|
||||
@@ -24,26 +24,26 @@ class InMemoryStorage implements Storage {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.storage[Object.keys(this.storage)[n]];
|
||||
return Object.keys(this.data)[n];
|
||||
}
|
||||
|
||||
public getItem(key: string) {
|
||||
return this.storage[key] || null;
|
||||
return this.data[key] || null;
|
||||
}
|
||||
|
||||
public setItem(key: string, value: string) {
|
||||
this.storage[key] = value;
|
||||
this.data[key] = value;
|
||||
}
|
||||
|
||||
public removeItem(key: string) {
|
||||
delete this.storage[key];
|
||||
delete this.data[key];
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return JSON.stringify(this.storage);
|
||||
return JSON.stringify(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
export default function createInMemoryStorage() {
|
||||
return new InMemoryStorage();
|
||||
export default function createInMemoryStorage(data?: Record<string, string>) {
|
||||
return new InMemoryStorage(data);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import prefixStorage from "./prefixStorage";
|
||||
|
||||
export default function createLocalStorage(prefix = "talk"): Storage {
|
||||
export default function createLocalStorage(prefix = "talk:"): Storage {
|
||||
return prefixStorage(window.localStorage, prefix);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import createInMemoryStorage from "./InMemoryStorage";
|
||||
import createPromisifiedStorage from "./PromisifiedStorage";
|
||||
|
||||
it("should set and unset values", () => {
|
||||
it("should set and unset values", async () => {
|
||||
const storage = createPromisifiedStorage(createInMemoryStorage());
|
||||
expect(storage.setItem("test", "value")).resolves.toBeUndefined();
|
||||
expect(storage.getItem("test")).resolves.toBe("value");
|
||||
await expect(storage.setItem("test", "value")).resolves.toBeUndefined();
|
||||
await expect(storage.getItem("test")).resolves.toBe("value");
|
||||
storage.removeItem("test");
|
||||
expect(storage.getItem("test")).resolves.toBeUndefined();
|
||||
await expect(storage.getItem("test")).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("should return length", async () => {
|
||||
const storage = createPromisifiedStorage(createInMemoryStorage());
|
||||
storage.setItem("a", "value");
|
||||
storage.setItem("b", "value");
|
||||
storage.setItem("c", "value");
|
||||
await expect(storage.length).resolves.toBe(3);
|
||||
});
|
||||
|
||||
it("should nth value", async () => {
|
||||
const storage = createPromisifiedStorage(createInMemoryStorage());
|
||||
storage.setItem("a", "a");
|
||||
storage.setItem("b", "b");
|
||||
storage.setItem("c", "c");
|
||||
await expect(storage.key(2)).resolves.toBe("c");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import createInMemoryStorage from "./InMemoryStorage";
|
||||
|
||||
export interface PromisifiedStorage {
|
||||
length: Promise<number>;
|
||||
|
||||
clear(): Promise<void>;
|
||||
|
||||
key(n: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* value = storage[key]
|
||||
*/
|
||||
@@ -23,6 +31,18 @@ class BackedPromisifedStorage implements PromisifiedStorage {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return Promise.resolve(this.storage.length);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
return Promise.resolve(this.storage.clear());
|
||||
}
|
||||
|
||||
public key(n: number) {
|
||||
return Promise.resolve(this.storage.key(n));
|
||||
}
|
||||
|
||||
public getItem(key: string) {
|
||||
return Promise.resolve(this.storage.getItem(key));
|
||||
}
|
||||
@@ -36,6 +56,8 @@ class BackedPromisifedStorage implements PromisifiedStorage {
|
||||
}
|
||||
}
|
||||
|
||||
export default function createPromisifiedStorage(storage: Storage) {
|
||||
export default function createPromisifiedStorage(
|
||||
storage: Storage = createInMemoryStorage()
|
||||
) {
|
||||
return new BackedPromisifedStorage(storage);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,49 @@ describe("PymStorage", () => {
|
||||
expect(promise).resolves.toBe("value");
|
||||
});
|
||||
|
||||
it("should get length", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.length;
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id, method, parameters } = JSON.parse(value);
|
||||
expect(method).toBe("length");
|
||||
expect(parameters).toEqual({});
|
||||
pym.listeners["pymStorage.localStorage.response"](
|
||||
JSON.stringify({ id, result: 3 })
|
||||
);
|
||||
expect(promise).resolves.toBe(3);
|
||||
});
|
||||
|
||||
it("should get key", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.key(2);
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id, method, parameters } = JSON.parse(value);
|
||||
expect(method).toBe("key");
|
||||
expect(parameters).toEqual({ n: 2 });
|
||||
pym.listeners["pymStorage.localStorage.response"](
|
||||
JSON.stringify({ id, result: "myKey" })
|
||||
);
|
||||
expect(promise).resolves.toBe("myKey");
|
||||
});
|
||||
|
||||
it("should clear", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.clear();
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id, method, parameters } = JSON.parse(value);
|
||||
expect(method).toBe("clear");
|
||||
expect(parameters).toEqual({});
|
||||
pym.listeners["pymStorage.localStorage.response"](JSON.stringify({ id }));
|
||||
expect(promise).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
describe("on error", () => {
|
||||
it("should reject set item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
|
||||
@@ -20,7 +20,7 @@ class PymStorage implements PromisifiedStorage {
|
||||
/** Requests method with parameters over pym. */
|
||||
private call<T>(
|
||||
method: string,
|
||||
parameters: { key: string; value?: string }
|
||||
parameters: Record<string, any> = {}
|
||||
): Promise<T> {
|
||||
const id = uuid();
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -55,6 +55,15 @@ class PymStorage implements PromisifiedStorage {
|
||||
this.listen();
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.call<number>("length");
|
||||
}
|
||||
public key(n: number) {
|
||||
return this.call<string | null>("key", { n });
|
||||
}
|
||||
public clear() {
|
||||
return this.call<void>("clear");
|
||||
}
|
||||
public setItem(key: string, value: string) {
|
||||
return this.call<void>("setItem", { key, value });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import prefixStorage from "./prefixStorage";
|
||||
|
||||
export default function createSessionStorage(prefix = "talk"): Storage {
|
||||
export default function createSessionStorage(prefix = "talk:"): Storage {
|
||||
return prefixStorage(window.sessionStorage, prefix);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`accepts predefined data 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"c\\":\\"2\\"}"`;
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should call clear 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"d\\":\\"3\\"}"`;
|
||||
@@ -1,54 +1,47 @@
|
||||
import sinon from "sinon";
|
||||
import createInMemoryStorage from "./InMemoryStorage";
|
||||
import prefixStorage from "./prefixStorage";
|
||||
|
||||
it("should call clear", () => {
|
||||
const storage = {
|
||||
clear: sinon.mock().once(),
|
||||
};
|
||||
it("should get nth key", () => {
|
||||
const storage = createInMemoryStorage({
|
||||
a: "0",
|
||||
b: "1",
|
||||
"talk:c": "2",
|
||||
d: "3",
|
||||
"talk:e": "4",
|
||||
});
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
const prefixed = prefixStorage(storage, "talk:");
|
||||
expect(prefixed.key(0)).toBe("talk:c");
|
||||
expect(prefixed.key(1)).toBe("talk:e");
|
||||
expect(prefixed.key(2)).toBeNull();
|
||||
});
|
||||
|
||||
it("should call clear", () => {
|
||||
const storage = createInMemoryStorage({
|
||||
a: "0",
|
||||
b: "1",
|
||||
"talk:c": "2",
|
||||
d: "3",
|
||||
"talk:e": "4",
|
||||
});
|
||||
|
||||
const prefixed = prefixStorage(storage, "talk:");
|
||||
prefixed.clear();
|
||||
storage.clear.verify();
|
||||
expect(storage.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call length", () => {
|
||||
const ret = 10;
|
||||
const storage = {
|
||||
get length() {
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
const storage = createInMemoryStorage({
|
||||
a: "0",
|
||||
b: "1",
|
||||
"talk:c": "2",
|
||||
d: "3",
|
||||
"talk:e": "4",
|
||||
});
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
expect(prefixed.length).toBe(ret);
|
||||
});
|
||||
|
||||
it("should call key", () => {
|
||||
const ret = "value";
|
||||
const storage = {
|
||||
key: sinon
|
||||
.mock()
|
||||
.withArgs(3)
|
||||
.returns(ret),
|
||||
};
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
expect(prefixed.key(3)).toBe(ret);
|
||||
(storage.key as any).verify();
|
||||
});
|
||||
|
||||
it("should call key", () => {
|
||||
const ret = "value";
|
||||
const storage = {
|
||||
key: sinon
|
||||
.mock()
|
||||
.withArgs(3)
|
||||
.returns(ret),
|
||||
};
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
expect(prefixed.key(3)).toBe(ret);
|
||||
(storage.key as any).verify();
|
||||
const prefixed = prefixStorage(storage, "talk:");
|
||||
expect(prefixed.length).toBe(2);
|
||||
});
|
||||
|
||||
it("should prefix setItem", () => {
|
||||
@@ -56,7 +49,7 @@ it("should prefix setItem", () => {
|
||||
setItem: sinon.mock().withArgs("talk:key", "value"),
|
||||
};
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
const prefixed = prefixStorage(storage as any, "talk:");
|
||||
prefixed.setItem("key", "value");
|
||||
storage.setItem.verify();
|
||||
});
|
||||
@@ -66,7 +59,7 @@ it("should prefix removeItem", () => {
|
||||
removeItem: sinon.mock().withArgs("talk:key"),
|
||||
};
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
const prefixed = prefixStorage(storage as any, "talk:");
|
||||
prefixed.removeItem("key");
|
||||
storage.removeItem.verify();
|
||||
});
|
||||
@@ -80,7 +73,7 @@ it("should prefix getItem", () => {
|
||||
.returns(ret),
|
||||
};
|
||||
|
||||
const prefixed = prefixStorage(storage as any, "talk");
|
||||
const prefixed = prefixStorage(storage as any, "talk:");
|
||||
expect(prefixed.getItem("key")).toBe(ret);
|
||||
(storage.getItem as any).verify();
|
||||
});
|
||||
|
||||
@@ -12,27 +12,50 @@ class PrefixedStorage implements Storage {
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.storage.length;
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.storage.length; i++) {
|
||||
if (this.storage.key(i)!.startsWith(this.prefix)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.storage.clear();
|
||||
const toBeDeleted = [];
|
||||
for (let i = 0; i < this.storage.length; i++) {
|
||||
const key = this.storage.key(i)!;
|
||||
if (key.startsWith(this.prefix)) {
|
||||
toBeDeleted.push(key);
|
||||
}
|
||||
}
|
||||
toBeDeleted.forEach(key => this.storage.removeItem(key));
|
||||
}
|
||||
|
||||
public key(n: number) {
|
||||
return this.storage.key(n);
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.storage.length; i++) {
|
||||
const key = this.storage.key(i)!;
|
||||
if (key.startsWith(this.prefix)) {
|
||||
if (count === n) {
|
||||
return key;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getItem(key: string) {
|
||||
return this.storage.getItem(`${this.prefix}:${key}`);
|
||||
return this.storage.getItem(`${this.prefix}${key}`);
|
||||
}
|
||||
|
||||
public setItem(key: string, value: string) {
|
||||
return this.storage.setItem(`${this.prefix}:${key}`, value);
|
||||
return this.storage.setItem(`${this.prefix}${key}`, value);
|
||||
}
|
||||
|
||||
public removeItem(key: string) {
|
||||
return this.storage.removeItem(`${this.prefix}:${key}`);
|
||||
return this.storage.removeItem(`${this.prefix}${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,24 +33,3 @@ it("Removes auth token from localStorage", () => {
|
||||
commit(environment, { authToken: null }, context as any);
|
||||
expect(context.localStorage.getItem("authToken")).toBeNull();
|
||||
});
|
||||
|
||||
it("Sets auth token to pymLocalStorage", async () => {
|
||||
const context = {
|
||||
pymLocalStorage: createInMemoryStorage(),
|
||||
localStorage: createInMemoryStorage(),
|
||||
};
|
||||
const authToken = "auth token";
|
||||
commit(environment, { authToken }, context as any);
|
||||
expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken);
|
||||
expect(await context.pymLocalStorage.getItem("authToken")).toEqual(authToken);
|
||||
});
|
||||
|
||||
it("Removes auth token from pymLocalStorage", async () => {
|
||||
const context = {
|
||||
pymLocalStorage: createInMemoryStorage(),
|
||||
localStorage: createInMemoryStorage(),
|
||||
};
|
||||
localStorage.setItem("authToken", "tmp");
|
||||
commit(environment, { authToken: null }, context as any);
|
||||
expect(await context.pymLocalStorage.getItem("authToken")).toBeNull();
|
||||
});
|
||||
|
||||
@@ -13,16 +13,15 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise<void>;
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: SetAuthTokenInput,
|
||||
{ localStorage, pymLocalStorage }: TalkContext
|
||||
{ localStorage }: TalkContext
|
||||
) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(input.authToken, "authToken");
|
||||
const storage = pymLocalStorage || localStorage;
|
||||
if (input.authToken) {
|
||||
storage.setItem("authToken", input.authToken);
|
||||
localStorage.setItem("authToken", input.authToken);
|
||||
} else {
|
||||
storage.removeItem("authToken");
|
||||
localStorage.removeItem("authToken");
|
||||
}
|
||||
// Increment auth revision to indicate a change in auth state.
|
||||
record.setValue(record.getValue("authRevision") + 1, "authRevision");
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import {
|
||||
createInMemoryStorage,
|
||||
createPromisifiedStorage,
|
||||
} from "talk-framework/lib/storage";
|
||||
|
||||
export default function createFakePymStorage() {
|
||||
return createPromisifiedStorage(createInMemoryStorage());
|
||||
}
|
||||
@@ -4,7 +4,6 @@ export {
|
||||
} from "./createRelayEnvironment";
|
||||
export { default as createFluentBundle } from "./createFluentBundle";
|
||||
export { default as createSinonStub } from "./createSinonStub";
|
||||
export { default as createFakePymStorage } from "./createFakePymStorage";
|
||||
export {
|
||||
default as removeFragmentRefs,
|
||||
NoFragmentRefs,
|
||||
|
||||
Reference in New Issue
Block a user