spyne 0.20.9 → 0.20.12
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/lib/spyne.esm.js +3 -3
- package/lib/spyne.esm.js.map +1 -1
- package/lib/spyne.umd.js +2 -2
- package/lib/spyne.umd.js.LICENSE.txt +2 -2
- package/package.json +1 -1
- package/src/spyne/channels/channel.js +10 -1
- package/src/spyne/channels/spyne-channel-ui.js +2 -0
- package/src/spyne/spyne-app.js +4 -2
- package/src/spyne/spyne.js +4 -1
- package/src/spyne/utils/channel-fetch-util.js +25 -1
- package/src/spyne/utils/sanitize-data.js +160 -0
- package/src/spyne/utils/spyne-app-properties.js +67 -0
- package/src/spyne/utils/spyne-utils-channel-route.js +17 -2
- package/src/spyne/views/dom-element-template.js +11 -0
package/package.json
CHANGED
|
@@ -412,6 +412,12 @@ export class Channel {
|
|
|
412
412
|
`channel name ${c} is not within ${registeredStreamNames}`)
|
|
413
413
|
const startSubscribe = (c) => {
|
|
414
414
|
const obs$ = this.streamsController.getStream(c).observer
|
|
415
|
+
if (obs$.source) {
|
|
416
|
+
obs$.source.channelName = channelName
|
|
417
|
+
} else {
|
|
418
|
+
obs$.channelName = channelName
|
|
419
|
+
}
|
|
420
|
+
|
|
415
421
|
if (payloadFilter !== undefined) {
|
|
416
422
|
return obs$.pipe(filter(payloadFilter))
|
|
417
423
|
}
|
|
@@ -453,11 +459,14 @@ export class Channel {
|
|
|
453
459
|
}
|
|
454
460
|
|
|
455
461
|
// 3) Map array → keyed object
|
|
462
|
+
// return combined$;
|
|
456
463
|
return combined$.pipe(
|
|
457
464
|
map(resultsArr => {
|
|
458
465
|
const obj = {}
|
|
459
466
|
channelsArr.forEach((item, idx) => {
|
|
460
|
-
const
|
|
467
|
+
const iterKey = typeof item === 'string' ? item : `channel_${idx}`
|
|
468
|
+
const cnKey = item?.source?.channelName || item?.source?.source?.channelName
|
|
469
|
+
const key = cnKey || iterKey
|
|
461
470
|
obj[key] = resultsArr[idx]
|
|
462
471
|
})
|
|
463
472
|
return obj
|
|
@@ -2,6 +2,7 @@ import { Channel } from './channel.js'
|
|
|
2
2
|
import { Observable } from 'rxjs'
|
|
3
3
|
import { map } from 'rxjs/operators'
|
|
4
4
|
import { equals, path, compose, prop, filter, replace, lensProp, over, omit, test, keys, either, toUpper } from 'ramda'
|
|
5
|
+
import { sanitizeEventTarget } from '../utils/sanitize-data.js'
|
|
5
6
|
|
|
6
7
|
export class SpyneChannelUI extends Channel {
|
|
7
8
|
/**
|
|
@@ -334,6 +335,7 @@ export class SpyneChannelUI extends Channel {
|
|
|
334
335
|
const { payload, srcElement } = obs
|
|
335
336
|
|
|
336
337
|
const event = obs.event
|
|
338
|
+
sanitizeEventTarget(event?.target)
|
|
337
339
|
// const payload = domStringMapToObject(srcElement?.el?.dataset) ?? obs.payload
|
|
338
340
|
this.sendChannelPayload(action, payload, srcElement, event)
|
|
339
341
|
}
|
package/src/spyne/spyne-app.js
CHANGED
|
@@ -3,9 +3,10 @@ import { ViewStream } from './views/view-stream.js'
|
|
|
3
3
|
import { SpyneUtilsChannelRoute } from './utils/spyne-utils-channel-route.js'
|
|
4
4
|
import { SpyneAppProperties } from './utils/spyne-app-properties.js'
|
|
5
5
|
import { sanitizeHTMLConfigure } from './utils/sanitize-html.js'
|
|
6
|
+
import { sanitizeDataConfigure } from './utils/sanitize-data.js'
|
|
6
7
|
|
|
7
8
|
const _channels = new ChannelsMap()
|
|
8
|
-
const version = '0.20.
|
|
9
|
+
const version = '0.20.12'
|
|
9
10
|
|
|
10
11
|
class SpyneApplication {
|
|
11
12
|
/**
|
|
@@ -42,7 +43,7 @@ class SpyneApplication {
|
|
|
42
43
|
init(config = {}, testMode = false) {
|
|
43
44
|
// this.channels = new ChannelsMap();
|
|
44
45
|
/*!
|
|
45
|
-
* Spyne 0.20.
|
|
46
|
+
* Spyne 0.20.12
|
|
46
47
|
* https://spynejs.org
|
|
47
48
|
*
|
|
48
49
|
* @license
|
|
@@ -141,6 +142,7 @@ class SpyneApplication {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
sanitizeHTMLConfigure(SpyneAppProperties.config);
|
|
145
|
+
sanitizeDataConfigure(SpyneAppProperties.config)
|
|
144
146
|
|
|
145
147
|
}
|
|
146
148
|
|
package/src/spyne/spyne.js
CHANGED
|
@@ -17,6 +17,7 @@ import { deepMerge } from './utils/deep-merge.js'
|
|
|
17
17
|
import { safeClone } from './utils/safe-clone.js'
|
|
18
18
|
import { SpyneAppProperties } from './utils/spyne-app-properties.js'
|
|
19
19
|
import { SpyneApp } from './spyne-app.js'
|
|
20
|
+
import sanitizeData, { sanitizeEventTarget } from './utils/sanitize-data.js'
|
|
20
21
|
|
|
21
22
|
export {
|
|
22
23
|
ViewStreamElement,
|
|
@@ -37,5 +38,7 @@ export {
|
|
|
37
38
|
SpyneAppProperties,
|
|
38
39
|
SpynePlugin,
|
|
39
40
|
deepMerge,
|
|
40
|
-
safeClone
|
|
41
|
+
safeClone,
|
|
42
|
+
sanitizeData,
|
|
43
|
+
sanitizeEventTarget
|
|
41
44
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { from } from 'rxjs'
|
|
2
2
|
import { flatMap, map, publish, tap } from 'rxjs/operators'
|
|
3
3
|
import { compose, prop, defaultTo, over, lensProp, has, propEq, when, propIs, allPass, assoc, pick, mergeDeepRight } from 'ramda'
|
|
4
|
+
import sanitizeData, { setSanitizeDataForceStrict } from './sanitize-data.js'
|
|
5
|
+
|
|
4
6
|
export class ChannelFetchUtil {
|
|
5
7
|
/**
|
|
6
8
|
* @module ChannelFetchUtil
|
|
@@ -47,6 +49,8 @@ export class ChannelFetchUtil {
|
|
|
47
49
|
*/
|
|
48
50
|
|
|
49
51
|
constructor(options, subscriber, testMode, CHANNEL_NAME) {
|
|
52
|
+
setSanitizeDataForceStrict(true)
|
|
53
|
+
|
|
50
54
|
const testSubscriber = (p) => console.log('FETCH RETURNED ', p)
|
|
51
55
|
|
|
52
56
|
this._mapFn = ChannelFetchUtil.setMapFn(options)
|
|
@@ -78,7 +82,27 @@ export class ChannelFetchUtil {
|
|
|
78
82
|
const metadata = { channelName, url, responseType, serverOptions }
|
|
79
83
|
|
|
80
84
|
return (data) => {
|
|
81
|
-
|
|
85
|
+
const disableSanitize = props?.disableSanitize === true
|
|
86
|
+
|
|
87
|
+
if (disableSanitize) {
|
|
88
|
+
const env = (typeof process !== 'undefined' && process.env && process.env.NODE_ENV)
|
|
89
|
+
? process.env.NODE_ENV
|
|
90
|
+
: 'development'
|
|
91
|
+
|
|
92
|
+
const msg = `SPYNE SECURITY WARNING — Sanitization disabled for fetch from ${url}. Data will enter the reactive stream unsanitized. Only use disableSanitize=true for trusted or controlled sources.`
|
|
93
|
+
|
|
94
|
+
if (env === 'production') {
|
|
95
|
+
throw new Error(`SpyneJS security policy violation: disableSanitize=true is not allowed in production (${url}).`)
|
|
96
|
+
} else {
|
|
97
|
+
console.warn(
|
|
98
|
+
`%c${msg}`,
|
|
99
|
+
'color:#f33;font-weight:bold;'
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sanitizedData = disableSanitize ? data : sanitizeData(data)
|
|
105
|
+
return mapMethod(sanitizedData, metadata)
|
|
82
106
|
}
|
|
83
107
|
}
|
|
84
108
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/utils/sanitize-data.js
|
|
2
|
+
import DOMPurify from 'dompurify'
|
|
3
|
+
import { SpyneAppProperties } from './spyne-app-properties.js'
|
|
4
|
+
|
|
5
|
+
let _sanitizeData
|
|
6
|
+
let isConfigured = false
|
|
7
|
+
let forceStrict = false // legacy compatibility toggle
|
|
8
|
+
|
|
9
|
+
/* ---------------------------------------------
|
|
10
|
+
* Mode configurations
|
|
11
|
+
* ------------------------------------------- */
|
|
12
|
+
const SAFE_FOR_RICH_TEXT = {
|
|
13
|
+
ALLOWED_TAGS: [
|
|
14
|
+
'a', 'b', 'i', 'em', 'strong', 'u', 'p', 'br', 'ul', 'ol', 'li',
|
|
15
|
+
'blockquote', 'pre', 'code', 'span', 'div', 'section', 'article', 'aside',
|
|
16
|
+
'header', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
17
|
+
'table', 'thead', 'tbody', 'tr', 'td', 'th', 'img', 'figure', 'figcaption',
|
|
18
|
+
'video', 'source'
|
|
19
|
+
],
|
|
20
|
+
ALLOWED_ATTR: [
|
|
21
|
+
'href', 'src', 'alt', 'title', 'width', 'height', 'style', 'target', 'rel',
|
|
22
|
+
'controls', 'poster', 'type', 'rows', 'cols'
|
|
23
|
+
],
|
|
24
|
+
ALLOW_DATA_ATTR: true,
|
|
25
|
+
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange'],
|
|
26
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'meta', 'link'],
|
|
27
|
+
SAFE_URL_PATTERN: /^(https?:|mailto:|tel:|data:image\/)/i
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SAFE_FOR_APP = {
|
|
31
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'link', 'meta'],
|
|
32
|
+
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange'],
|
|
33
|
+
ALLOW_DATA_ATTR: false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ---------------------------------------------
|
|
37
|
+
* Determine sanitization mode from SpyneAppProperties
|
|
38
|
+
* ------------------------------------------- */
|
|
39
|
+
const resolveDefaultMode = () => {
|
|
40
|
+
try {
|
|
41
|
+
const mode = SpyneAppProperties?.mode
|
|
42
|
+
if (mode === 'authoring' || mode === 'development') return 'richtext'
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Fallback: assume production strict mode
|
|
45
|
+
}
|
|
46
|
+
return 'app'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* --------------------------------------------- */
|
|
50
|
+
function makeSanitizeFn(mode = 'app') {
|
|
51
|
+
const cfg = mode === 'richtext' ? SAFE_FOR_RICH_TEXT : SAFE_FOR_APP
|
|
52
|
+
|
|
53
|
+
return (html) => {
|
|
54
|
+
let clean = DOMPurify.sanitize(html, cfg)
|
|
55
|
+
|
|
56
|
+
// In richtext mode, enforce safe URL schemes for href/src
|
|
57
|
+
if (mode === 'richtext') {
|
|
58
|
+
clean = clean.replace(/<(a|img)\b([^>]+?)>/gi, (match) => {
|
|
59
|
+
return match.replace(/\b(href|src)="([^"]*)"/gi, (m, attr, val) => {
|
|
60
|
+
return SAFE_FOR_RICH_TEXT.SAFE_URL_PATTERN.test(val) ? m : `${attr}=""`
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return clean
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ---------------------------------------------
|
|
69
|
+
* Configure once (kept for compatibility)
|
|
70
|
+
* ------------------------------------------- */
|
|
71
|
+
const sanitizeDataConfigure = (config = {}) => {
|
|
72
|
+
if (isConfigured) {
|
|
73
|
+
console.warn('sanitizeData is already configured. Reconfiguration is not allowed.')
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { strict = false } = config
|
|
78
|
+
const processFactory = (mode) => {
|
|
79
|
+
const sanitizeStr = makeSanitizeFn(mode)
|
|
80
|
+
const process = (val) => {
|
|
81
|
+
if (Array.isArray(val)) return val.map(process)
|
|
82
|
+
if (val && typeof val === 'object') {
|
|
83
|
+
const out = {}
|
|
84
|
+
for (const [k, v] of Object.entries(val)) {
|
|
85
|
+
out[k] = (typeof v === 'string') ? sanitizeStr(v) : process(v)
|
|
86
|
+
}
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
return (typeof val === 'string') ? sanitizeStr(val) : val
|
|
90
|
+
}
|
|
91
|
+
return process
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_sanitizeData = (data, opts = {}) => {
|
|
95
|
+
const { disableSanitize = false, mode } = opts
|
|
96
|
+
const legacyStrict = forceStrict || strict
|
|
97
|
+
const effectiveMode = mode || (legacyStrict ? 'app' : resolveDefaultMode())
|
|
98
|
+
if (disableSanitize) return data
|
|
99
|
+
return processFactory(effectiveMode)(data)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
sanitizeDataConfigure.sanitizeDataForce = (data, mode) => {
|
|
103
|
+
const effective = mode || resolveDefaultMode()
|
|
104
|
+
return processFactory(effective)(data)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
isConfigured = true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ---------------------------------------------
|
|
111
|
+
* Configurable runtime sanitizer
|
|
112
|
+
* ------------------------------------------- */
|
|
113
|
+
const sanitizeData = (data, opts = {}) => {
|
|
114
|
+
if (!isConfigured) {
|
|
115
|
+
throw new Error('sanitizeData is not configured. Call sanitizeDataConfigure() first.')
|
|
116
|
+
}
|
|
117
|
+
return _sanitizeData(data, opts)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ---------------------------------------------
|
|
121
|
+
* Always-on sanitizer with environment default
|
|
122
|
+
* ------------------------------------------- */
|
|
123
|
+
const sanitizeDataForce = (data, mode) => {
|
|
124
|
+
if (!isConfigured || !sanitizeDataConfigure.sanitizeDataForce) {
|
|
125
|
+
const sanitizeStr = makeSanitizeFn(mode || resolveDefaultMode())
|
|
126
|
+
return (typeof data === 'string') ? sanitizeStr(data) : data
|
|
127
|
+
}
|
|
128
|
+
return sanitizeDataConfigure.sanitizeDataForce(data, mode)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ---------------------------------------------
|
|
132
|
+
* Event-target sanitizer
|
|
133
|
+
* ------------------------------------------- */
|
|
134
|
+
const sanitizeEventTarget = (el, mode) => {
|
|
135
|
+
if (!el) return
|
|
136
|
+
const effectiveMode = mode || resolveDefaultMode()
|
|
137
|
+
const sanitizeStr = makeSanitizeFn(effectiveMode)
|
|
138
|
+
const UNSAFE_RE = /<(?:script|iframe|object|embed|form|input|button|link|meta)[^>]*>|on\w+=|javascript:/i
|
|
139
|
+
|
|
140
|
+
if (typeof el.value === 'string' && UNSAFE_RE.test(el.value)) {
|
|
141
|
+
const clean = sanitizeStr(el.value)
|
|
142
|
+
if (clean !== el.value) el.value = clean
|
|
143
|
+
}
|
|
144
|
+
if (el.isContentEditable && typeof el.innerHTML === 'string' && UNSAFE_RE.test(el.innerHTML)) {
|
|
145
|
+
const clean = sanitizeStr(el.innerHTML)
|
|
146
|
+
if (clean !== el.innerHTML) el.innerHTML = clean
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* --------------------------------------------- */
|
|
151
|
+
const setSanitizeDataForceStrict = (bool = true) => { forceStrict = !!bool }
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
sanitizeDataConfigure,
|
|
155
|
+
sanitizeData,
|
|
156
|
+
sanitizeDataForce,
|
|
157
|
+
sanitizeEventTarget,
|
|
158
|
+
setSanitizeDataForceStrict
|
|
159
|
+
}
|
|
160
|
+
export default sanitizeData
|
|
@@ -7,6 +7,7 @@ let _channels
|
|
|
7
7
|
let _channelsMap
|
|
8
8
|
let _initialized
|
|
9
9
|
let _debug = true
|
|
10
|
+
let _enableCMSProxies = false
|
|
10
11
|
const _excludeChannelsFromConsole = []
|
|
11
12
|
/* eslint-disable */
|
|
12
13
|
let _linksData
|
|
@@ -267,6 +268,72 @@ class SpyneAppPropertiesClass {
|
|
|
267
268
|
_navLinks = arr
|
|
268
269
|
}
|
|
269
270
|
|
|
271
|
+
get enableCMSProxies(){
|
|
272
|
+
return _enableCMSProxies
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
set enableCMSProxies(bool=true){
|
|
276
|
+
_enableCMSProxies = Boolean(bool);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
setCMSProxyMethod(fn){
|
|
280
|
+
// this.formatTemplateForProxyData = fn;
|
|
281
|
+
this.enableCMSProxies = true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// SpyneJS Enterprise Code Start
|
|
285
|
+
formatTemplateForProxyData(tmpl) {
|
|
286
|
+
const isPrimitiveTag = str => /({{\.\*?}})/.test(str);
|
|
287
|
+
|
|
288
|
+
const reLines2 = /(({{(?!loopNum|loopIndex))(.*?)(}}))/gm
|
|
289
|
+
const reStr = '(?<=>)(.*?)(({{)(.*)(}}))'
|
|
290
|
+
|
|
291
|
+
const reLines1 = new RegExp(reStr, 'gm')
|
|
292
|
+
|
|
293
|
+
const formatCmsParam = (str) => {
|
|
294
|
+
const isArrPrimitive = isPrimitiveTag(str)
|
|
295
|
+
|
|
296
|
+
const getKeyAndDataId = (s) => {
|
|
297
|
+
const re = /({{)([\w_]+(\.))*?(\w+)(}})/gm // this is old, will be deprecated.
|
|
298
|
+
const reNestedData = /^\{\{([^}]+?)\.[^.}]+}}$/gm
|
|
299
|
+
|
|
300
|
+
let dataId = '__cms__dataId'
|
|
301
|
+
const key = isArrPrimitive ? '{{loopIndex}}' : String(s).replace(re, '$4')
|
|
302
|
+
if (/(\w\.)/.test(s)) {
|
|
303
|
+
dataId = String(s).replace(reNestedData, '$1.') + dataId
|
|
304
|
+
}
|
|
305
|
+
return { key, dataId }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const { key, dataId } = getKeyAndDataId(str)
|
|
309
|
+
|
|
310
|
+
let prefix = `<spyne-cms-item data-cms-id="{{${dataId}}}" data-cms-key="${key}"`
|
|
311
|
+
|
|
312
|
+
// IF AN ARRAY PRIMITIVE CHECK FOR ORIGINAL ARRAY POSITION
|
|
313
|
+
if (isArrPrimitive) {
|
|
314
|
+
prefix += ' data-cms-orig-key="{{origKey}}"'
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// CREATE PROXY TAG
|
|
318
|
+
const suffix = '</spyne-cms-item>'
|
|
319
|
+
const param = isArrPrimitive ? '{{spyneLoopKey}}' : str
|
|
320
|
+
return `${prefix}><spyne-cms-item-hitbox></spyne-cms-item-hitbox><spyne-cms-item-text>${param}</spyne-cms-item-text>${suffix}`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const reSwapTags = (str, m1, m2, m3) => {
|
|
324
|
+
return formatCmsParam(str)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const reMethod = (str, m1, m2, m3, m4) => {
|
|
328
|
+
const m2Updated = String(m2).replace(reLines2, reSwapTags)
|
|
329
|
+
return `${m1}${m2Updated}`
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return String(tmpl).replace(reLines1, reMethod)
|
|
333
|
+
}
|
|
334
|
+
// SpyneJS Enterprise Code End
|
|
335
|
+
|
|
336
|
+
|
|
270
337
|
tempGetChannelsInstance() {
|
|
271
338
|
|
|
272
339
|
}
|
|
@@ -93,6 +93,20 @@ export class SpyneUtilsChannelRoute {
|
|
|
93
93
|
static addRouteDatasets(channelRouteObj) {
|
|
94
94
|
// channelRouteObj.type='query';
|
|
95
95
|
|
|
96
|
+
function removeUndefined(arr) {
|
|
97
|
+
return arr.map(obj => {
|
|
98
|
+
for (const key in obj) {
|
|
99
|
+
if (key === undefined || key === 'undefined' || obj[key] === undefined) {
|
|
100
|
+
delete obj[key]
|
|
101
|
+
} else if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
102
|
+
// recurse if nested object
|
|
103
|
+
obj[key] = removeUndefined([obj[key]])[0]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return obj
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
96
110
|
const { type, isHash } = channelRouteObj
|
|
97
111
|
|
|
98
112
|
// create href and check to see if need to convert to hash href links
|
|
@@ -163,10 +177,11 @@ export class SpyneUtilsChannelRoute {
|
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
const reducedArr = createInitialValFn([], channelRouteObj.routes)
|
|
166
|
-
|
|
180
|
+
let routeDatasetsArr = flatten(reducedArr)
|
|
181
|
+
routeDatasetsArr = removeUndefined(routeDatasetsArr)
|
|
167
182
|
|
|
168
183
|
const getNavProps = (datasetsArr) => {
|
|
169
|
-
const exclude = reject(includes(__, ['title', 'href', 'navLevel']))
|
|
184
|
+
const exclude = reject(includes(__, ['title', 'href', 'navLevel', 'undefined']))
|
|
170
185
|
const getMainKeys = compose(uniq, exclude, flatten, map(keys))
|
|
171
186
|
return getMainKeys(datasetsArr)
|
|
172
187
|
}
|
|
@@ -140,6 +140,17 @@ export class DomElementTemplate {
|
|
|
140
140
|
this.isProxyData = data.__cms__isProxy === true
|
|
141
141
|
this.testMode = opts?.testMode
|
|
142
142
|
|
|
143
|
+
/*
|
|
144
|
+
if (this.isProxyData === true) {
|
|
145
|
+
console.log('PROXY IS ', data.__cms__rootData)
|
|
146
|
+
this.template = DomElementTemplate.formatTemplateForProxyData(this.template)
|
|
147
|
+
}
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
if (SpyneAppProperties.enableCMSProxies && this.isProxyData === true) {
|
|
151
|
+
this.template = SpyneAppProperties.formatTemplateForProxyData(this.template)
|
|
152
|
+
}
|
|
153
|
+
|
|
143
154
|
const checkForArrayData = () => {
|
|
144
155
|
if (is(Array, data) === true) {
|
|
145
156
|
data = { spyneData:data }
|