@json-editor/json-editor 2.8.0 → 2.9.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@json-editor/json-editor",
3
3
  "title": "JSONEditor",
4
4
  "description": "JSON Schema based editor",
5
- "version": "2.8.0",
5
+ "version": "2.9.0-beta.0",
6
6
  "main": "dist/jsoneditor.js",
7
7
  "author": {
8
8
  "name": "Jeremy Dorn",
@@ -421,27 +421,40 @@ export class SchemaLoader {
421
421
  waiting++
422
422
 
423
423
  let url = this._joinUrl(uri, fileBase)
424
- const response = await new Promise(resolve => {
425
- const r = new XMLHttpRequest()
426
- if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
427
- r.overrideMimeType('application/json')
428
- r.open('GET', url, true)
429
- r.onload = () => {
430
- resolve(r)
424
+
425
+ let externalSchema
426
+ if (this.options.ajax_cache_responses) {
427
+ const schemaFromCache = this.cacheGet(url)
428
+ if (schemaFromCache) {
429
+ externalSchema = schemaFromCache
431
430
  }
432
- r.onerror = (e) => {
433
- resolve(undefined)
431
+ }
432
+
433
+ if (!externalSchema) {
434
+ const response = await new Promise(resolve => {
435
+ const r = new XMLHttpRequest()
436
+ if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
437
+ r.overrideMimeType('application/json')
438
+ r.open('GET', url, true)
439
+ r.onload = () => {
440
+ resolve(r)
441
+ }
442
+ r.onerror = (e) => {
443
+ resolve(undefined)
444
+ }
445
+ r.send()
446
+ })
447
+ if (typeof response === 'undefined') throw new Error(`Failed to fetch ref via ajax - ${uri}`)
448
+ try {
449
+ externalSchema = JSON.parse(response.responseText)
450
+ if (this.options.ajax_cache_responses) {
451
+ this.cacheSet(url, externalSchema)
452
+ }
453
+ } catch (e) {
454
+ // eslint-disable-next-line no-console
455
+ console.log(e)
456
+ throw new Error(`Failed to parse external ref ${url}`)
434
457
  }
435
- r.send()
436
- })
437
- if (typeof response === 'undefined') throw new Error(`Failed to fetch ref via ajax - ${uri}`)
438
- let externalSchema
439
- try {
440
- externalSchema = JSON.parse(response.responseText)
441
- } catch (e) {
442
- // eslint-disable-next-line no-console
443
- console.log(e)
444
- throw new Error(`Failed to parse external ref ${url}`)
445
458
  }
446
459
 
447
460
  if (!(typeof externalSchema === 'boolean' || typeof externalSchema === 'object') || externalSchema === null || Array.isArray(externalSchema)) {
@@ -528,4 +541,84 @@ export class SchemaLoader {
528
541
  })
529
542
  return extended
530
543
  }
544
+
545
+ /**
546
+ * Gets a cache key namespaced for JSON Editor.
547
+ *
548
+ * @param {*} key
549
+ * The schema's key, e.g., URL.
550
+ * @returns {string}
551
+ * A namespaced cache key, by prefixing "je-cache::".
552
+ */
553
+ getCacheKey (key) {
554
+ return ['je-cache', key].join('::')
555
+ }
556
+
557
+ /**
558
+ * Returns the schema cache buster from JSON Editor settings.
559
+ *
560
+ * @returns {string}
561
+ * The configured cache buster, if any. Otherwise, returns the current date
562
+ * in ISO 8601 simplified format (e.g., 2011-10-05 for October 5, 2011).
563
+ */
564
+ getCacheBuster () {
565
+ return this.options.ajax_cache_buster || new Date().toISOString().slice(0, 10)
566
+ }
567
+
568
+ /**
569
+ * Sets a schema into localStorage cache.
570
+ *
571
+ * @param {string} key
572
+ * The schema's key, e.g., URL.
573
+ * @param {mixed} data
574
+ * The schema to store. Can be any data type.
575
+ */
576
+ cacheSet (key, data) {
577
+ try {
578
+ window.localStorage.setItem(this.getCacheKey(key), JSON.stringify({
579
+ cacheBuster: this.getCacheBuster(),
580
+ schema: data
581
+ }))
582
+ } catch (e) {
583
+ // eslint-disable-next-line no-console
584
+ console.error(e)
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Fetches a schema from localStorage cache.
590
+ *
591
+ * @param {string} key
592
+ * The schema's key, e.g., URL.
593
+ *
594
+ * @returns {mixed}
595
+ * If found, returns the schema.
596
+ */
597
+ cacheGet (key) {
598
+ try {
599
+ const resultRaw = window.localStorage.getItem(this.getCacheKey(key))
600
+ if (resultRaw) {
601
+ const resultDecoded = JSON.parse(resultRaw)
602
+ if (resultDecoded.cacheBuster && resultDecoded.schema) {
603
+ if (resultDecoded.cacheBuster === this.getCacheBuster()) {
604
+ return resultDecoded.schema
605
+ }
606
+ }
607
+ this.cacheDelete(key)
608
+ }
609
+ } catch (e) {
610
+ // eslint-disable-next-line no-console
611
+ console.error(e)
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Deletes a schema from localStorage cache.
617
+ *
618
+ * @param {string} key
619
+ * The schema's key, e.g., URL.
620
+ */
621
+ cacheDelete (key) {
622
+ window.localStorage.removeItem(this.getCacheKey(key))
623
+ }
531
624
  }
@@ -247,6 +247,27 @@ Scenario('should load internal schema definitions, external schema definitions a
247
247
 
248
248
  // external schema properties
249
249
  I.waitForElement('[data-schemapath="root.link.street_address"]')
250
+
251
+ const currentUrl = await I.grabCurrentUrl()
252
+ const currentPath = currentUrl.replace('references.html', '')
253
+
254
+ // Ensures that external schemas were stored in cache. (This does not assert that the loader actually fetched them from cache.)
255
+ const schemaPaths = [
256
+ '../fixtures/string.json',
257
+ '../fixtures/definitions.json',
258
+ '../fixtures/basic_person.json',
259
+ '../fixtures/person.json',
260
+ ]
261
+ for (const path of schemaPaths) {
262
+ let key = 'je-cache::' + currentPath + path;
263
+
264
+ const item = await I.executeScript(function (storageKey) {
265
+ return window.localStorage.getItem(storageKey);
266
+ }, key)
267
+ const itemDecoded = JSON.parse(item)
268
+ assert.equal(itemDecoded.cacheBuster, 'abc123');
269
+ assert(itemDecoded, 'Cached schema found');
270
+ }
250
271
  })
251
272
 
252
273
  Scenario('should override error messages if specified in schema options @core @errors-messages', async (I) => {
@@ -58,6 +58,12 @@
58
58
  // Enable fetching schemas via ajax
59
59
  ajax: true,
60
60
 
61
+ // Enable caching schemas from via ajax
62
+ ajax_cache_responses: true,
63
+
64
+ // The cache-buster for cached caches
65
+ ajax_cache_buster: 'abc123',
66
+
61
67
  // The schema for the editor
62
68
  schema: {
63
69
  $schema: "https://json-schema.org/draft-04/schema",
@@ -80,7 +80,7 @@ describe('SchemaLoader', () => {
80
80
  })
81
81
 
82
82
  describe('when external absolute ref exists', () => {
83
- it('should set oprion { ajax: true }', async () => {
83
+ it('should set option { ajax: true }', async () => {
84
84
  const response = {
85
85
  type: 'string',
86
86
  minLength: 4
@@ -117,7 +117,7 @@ describe('SchemaLoader', () => {
117
117
  })
118
118
 
119
119
  describe('when external relative $ref exists', () => {
120
- it('should set oprion { ajax: true }', async () => {
120
+ it('should set option { ajax: true }', async () => {
121
121
  const response = {
122
122
  type: 'string',
123
123
  minLength: 4
@@ -467,4 +467,110 @@ describe('SchemaLoader', () => {
467
467
  expect(Object.keys(loader.refs).length).toBe(1)
468
468
  })
469
469
  })
470
+
471
+ describe('when schemas caching is enabled', () => {
472
+ beforeEach(() => {
473
+ // Mocks window.localStorage system.
474
+ // Thanks to https://stackoverflow.com/a/32911774.
475
+ var localStorageMock = (function () {
476
+ var store = {}
477
+ return {
478
+ getItem: function (key) {
479
+ return store[key]
480
+ },
481
+ setItem: function (key, value) {
482
+ store[key] = value.toString()
483
+ },
484
+ clear: function () {
485
+ store = {}
486
+ },
487
+ removeItem: function (key) {
488
+ delete store[key]
489
+ }
490
+ }
491
+ })()
492
+ Object.defineProperty(window, 'localStorage', { value: localStorageMock })
493
+ })
494
+ it('should store and retrieve cached items', async () => {
495
+ const schema = {
496
+ type: 'string',
497
+ minLength: 4
498
+ }
499
+ const cacheKey = 'myItem'
500
+ loader = new SchemaLoader({ ajax: true, ajax_cache_responses: true, ajax_cache_buster: 'abc123' })
501
+ loader.cacheSet(cacheKey, schema)
502
+
503
+ const schemaCached = loader.cacheGet(cacheKey)
504
+ expect(schemaCached).toEqual(schema)
505
+ })
506
+ it('should not retrieve cached item with invalid cache buster', async () => {
507
+ const schema = {
508
+ type: 'string',
509
+ minLength: 4
510
+ }
511
+ const cacheKey = 'myItem'
512
+ loader = new SchemaLoader({ ajax: true, ajax_cache_responses: true, ajax_cache_buster: 'abc123' })
513
+ loader.cacheSet(cacheKey, schema)
514
+
515
+ loader.options.ajax_cache_buster = 'not-abc123'
516
+ const schemaCached = loader.cacheGet(cacheKey)
517
+ expect(schemaCached).toBeUndefined()
518
+ })
519
+ it('should fetch schemas from cache { ajax: true, ajax_cache_responses: true }', async () => {
520
+ // Runs two passes, the first to warm cache and second to fetch from cache.
521
+ const response = {
522
+ type: 'string',
523
+ minLength: 4
524
+ }
525
+ const server = createFakeServer()
526
+ server.autoRespond = true
527
+ window.XMLHttpRequest = server.xhr
528
+ server.respondWith('/string.json', [
529
+ 200,
530
+ { 'Content-Type': 'application/json' },
531
+ JSON.stringify(response)
532
+ ])
533
+ fetchUrl =
534
+ document.location.origin + document.location.pathname.toString()
535
+ loader = new SchemaLoader({ ajax: true, ajax_cache_responses: true })
536
+ fileBase = loader._getFileBase(document.location.toString())
537
+
538
+ // Pass one: Warm the cache.
539
+ const schema = {
540
+ type: 'object',
541
+ properties: {
542
+ fname: { $ref: '/string.json' },
543
+ lname: { $ref: '/string.json' }
544
+ }
545
+ }
546
+ await loader.load(
547
+ schema,
548
+ fetchUrl,
549
+ fileBase
550
+ )
551
+ const urls = Object.keys(loader.refs)
552
+ expect(urls.length).toEqual(1)
553
+
554
+ // Tears down the mock Ajax endpoint to ensure any schemas get fetched from cache.
555
+ server.restore()
556
+
557
+ // Pass two: Should fetch external schemas from cache.
558
+ // Requires a fresh loader because SchemaLoader.refs can return stale data.
559
+ const loaderFresh = new SchemaLoader({ ajax: true, ajax_cache_responses: true })
560
+ const schemaFromCache = {
561
+ type: 'object',
562
+ properties: {
563
+ fname: { $ref: '/string.json' },
564
+ lname: { $ref: '/string.json' }
565
+ }
566
+ }
567
+ await loaderFresh.load(
568
+ schemaFromCache,
569
+ fetchUrl,
570
+ fileBase
571
+ )
572
+ const urlsFromCache = Object.keys(loaderFresh.refs)
573
+ expect(urlsFromCache.length).toEqual(1)
574
+ })
575
+ })
470
576
  })