Skip to content

Commit 37744f0

Browse files
committed
Always delegate JWT to storage backend
Previously we had an incomplete implementation of the CredentialStorageBackend concept, as we were initializing from and storing to it, but we also memoized it into the model class itself, which meant that particularly when using localStorage, an update from outside of JSORM wouldn't track into already initialized JSORM models. Now it's possible to pass an InMemoryStorageBackend instead of the default localStorage, which will isolate a particular model and prevent it from persisting.
1 parent 54029f6 commit 37744f0

File tree

8 files changed

+285
-205
lines changed

8 files changed

+285
-205
lines changed

src/credential-storage.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ export interface StorageBackend {
44
removeItem(key: string): void
55
}
66

7-
export class NullStorageBackend implements StorageBackend {
8-
getItem(key: string) {
9-
return null
7+
export class InMemoryStorageBackend implements StorageBackend {
8+
private _data : Record<string, string | undefined>
9+
10+
constructor() {
11+
this._data = {}
12+
}
13+
14+
getItem(key: string) : string | null {
15+
return this._data[key] || null // Cast undefined to null
1016
}
1117
setItem(key: string, value: string | undefined) {
12-
/*noop*/
18+
this._data[key] = value
1319
}
1420
removeItem(key: string) {
15-
/*noop*/
21+
delete this._data[key]
1622
}
1723
}
1824

@@ -22,36 +28,34 @@ let defaultBackend: StorageBackend
2228
try {
2329
defaultBackend = localStorage
2430
} catch (e) {
25-
defaultBackend = new NullStorageBackend()
31+
defaultBackend = new InMemoryStorageBackend()
2632
}
2733

2834
export class CredentialStorage {
29-
private _jwtKey: string | false
35+
private _jwtKey: string
3036
private _backend: StorageBackend
3137

3238
constructor(
33-
jwtKey: string | false,
39+
jwtKey: string,
3440
backend: StorageBackend = defaultBackend
3541
) {
3642
this._jwtKey = jwtKey
3743
this._backend = backend
3844
}
3945

40-
getJWT(): string | null {
41-
if (this._jwtKey) {
42-
return this._backend.getItem(this._jwtKey)
43-
} else {
44-
return null
45-
}
46+
get backend() : StorageBackend {
47+
return this._backend
48+
}
49+
50+
getJWT(): string | undefined {
51+
return this._backend.getItem(this._jwtKey) || undefined
4652
}
4753

4854
setJWT(value: string | undefined | null): void {
49-
if (this._jwtKey) {
50-
if (value) {
51-
this._backend.setItem(this._jwtKey, value)
52-
} else {
53-
this._backend.removeItem(this._jwtKey)
54-
}
55+
if (value) {
56+
this._backend.setItem(this._jwtKey, value)
57+
} else {
58+
this._backend.removeItem(this._jwtKey)
5559
}
5660
}
5761
}

src/model.ts

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { WritePayload } from "./util/write-payload"
88
import { flipEnumerable, getNonEnumerables } from "./util/enumerables"
99
import {
1010
CredentialStorage,
11-
NullStorageBackend,
11+
InMemoryStorageBackend,
1212
StorageBackend
1313
} from "./credential-storage"
1414
import { IDMap } from "./id-map"
@@ -118,11 +118,18 @@ export const applyModelConfig = <T extends typeof JSORMBase>(
118118
let k: keyof ModelConfigurationOptions
119119

120120
for (k in config) {
121-
if (config.hasOwnProperty(k)) {
121+
if (config.hasOwnProperty(k) && k !== 'jwt') {
122122
ModelClass[k] = config[k]
123123
}
124124
}
125125

126+
// Run this one last, since either the Credential Storage strategy
127+
// or the store key could be set in options alongside this, otherwise
128+
// this is dependent on option declaration order
129+
if (config.jwt) {
130+
ModelClass.setJWT(config.jwt)
131+
}
132+
126133
if (ModelClass.isBaseClass === undefined) {
127134
ModelClass.setAsBase()
128135
} else if (ModelClass.isBaseClass === true) {
@@ -136,7 +143,6 @@ export class JSORMBase {
136143
static jsonapiType?: string
137144
static endpoint: string
138145
static isBaseClass: boolean
139-
static jwt?: string
140146
static keyCase: KeyCase = { server: "snake", client: "camel" }
141147
static strictAttributes: boolean = false
142148
static logger: ILogger = defaultLogger
@@ -148,36 +154,48 @@ export class JSORMBase {
148154
static currentClass: typeof JSORMBase = JSORMBase
149155
static beforeFetch: BeforeFilter | undefined
150156
static afterFetch: AfterFilter | undefined
151-
static jwtStorage: string | false = "jwt"
152157

153158
private static _typeRegistry: JsonapiTypeRegistry
154159
private static _IDMap: IDMap
155160
private static _middlewareStack: MiddlewareStack
156-
private static _credentialStorageBackend?: StorageBackend
157-
private static _credentialStorage?: CredentialStorage
161+
private static _credentialStorageBackend: StorageBackend
162+
private static _credentialStorage: CredentialStorage
163+
private static _jwtStorage: string | false = "jwt"
158164

159165
static get credentialStorage(): CredentialStorage {
160-
if (!this._credentialStorage) {
161-
if (!this._credentialStorageBackend) {
162-
if (this.jwtStorage && typeof localStorage !== "undefined") {
163-
this._credentialStorageBackend = localStorage
164-
} else {
165-
this._credentialStorageBackend = new NullStorageBackend()
166-
}
167-
}
166+
return this._credentialStorage
167+
}
168168

169-
this._credentialStorage = new CredentialStorage(
170-
this.jwtStorage,
171-
this._credentialStorageBackend
172-
)
169+
static set jwtStorage(val : string | false) {
170+
if (val !== this._jwtStorage) {
171+
this._jwtStorage = val
172+
this.credentialStorageBackend = this._credentialStorageBackend
173173
}
174+
}
174175

175-
return this._credentialStorage
176+
static get jwtStorage() : string | false {
177+
return this._jwtStorage
176178
}
177179

178-
static set credentialStorageBackend(backend: StorageBackend | undefined) {
180+
static set credentialStorageBackend(backend: StorageBackend) {
179181
this._credentialStorageBackend = backend
180-
this._credentialStorage = undefined
182+
183+
this._credentialStorage = new CredentialStorage(
184+
this.jwtStorage || 'jwt',
185+
this._credentialStorageBackend
186+
)
187+
}
188+
189+
static get credentialStorageBackend() : StorageBackend {
190+
return this._credentialStorageBackend
191+
}
192+
193+
static initializeCredentialStorage() {
194+
if (this.jwtStorage && typeof localStorage !== "undefined") {
195+
this.credentialStorageBackend = localStorage
196+
} else {
197+
this.credentialStorageBackend = new InMemoryStorageBackend()
198+
}
181199
}
182200

183201
/*
@@ -228,9 +246,6 @@ export class JSORMBase {
228246
if (!this._IDMap) {
229247
this._IDMap = new IDMap()
230248
}
231-
232-
const jwt = this.credentialStorage.getJWT()
233-
this.setJWT(jwt)
234249
}
235250

236251
static isSubclassOf(maybeSuper: typeof JSORMBase): boolean {
@@ -753,22 +768,16 @@ export class JSORMBase {
753768
}
754769

755770
static setJWT(token: string | undefined | null): void {
756-
if (this.baseClass === undefined) {
757-
throw new Error(`Cannot set JWT on ${this.name}: No base class present.`)
758-
}
759-
760-
this.baseClass.jwt = token || undefined
761771
this.credentialStorage.setJWT(token)
762772
}
763773

764774
static getJWT(): string | undefined {
765-
const owner = this.baseClass
766-
767-
if (owner) {
768-
return owner.jwt
769-
}
775+
return this.credentialStorage.getJWT()
770776
}
771777

778+
static get jwt(): string | undefined { return this.getJWT() }
779+
static set jwt(token: string | undefined) { this.setJWT(token) }
780+
772781
static generateAuthHeader(jwt: string): string {
773782
return `Token token="${jwt}"`
774783
}
@@ -897,6 +906,7 @@ export class JSORMBase {
897906
}
898907

899908
;(<any>JSORMBase.prototype).klass = JSORMBase
909+
JSORMBase.initializeCredentialStorage()
900910

901911
export const isModelClass = (arg: any): arg is typeof JSORMBase => {
902912
if (!arg) {

0 commit comments

Comments
 (0)