Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/nuxt-app/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ export default defineNuxtConfig({
},

compatibilityDate: '2025-03-20'
})
})
3 changes: 2 additions & 1 deletion packages/nuxt-ripple-analytics/plugins/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ const setupDataLayer = (featureFlags: IRplFeatureFlags) => {
const setupGTM = (GTM_ID: string) => {
if (GTM_ID) {
// Add tracking code to page with loadScript
loadScript(GTM_ID, { defer: true, compatibility: false })
const nonce = useNonce()
loadScript(GTM_ID, { defer: true, compatibility: false, nonce })
}
}

Expand Down
16 changes: 15 additions & 1 deletion packages/nuxt-ripple/components/TideBaseLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,21 @@ const footerNav = computed(() => {
await nuxtApp.callHook('tide:page', props)

const theme = useTideSiteTheme(props.site)
useTideHideAlerts()

// useScript(
// {
// src: '/scripts/alerts.js',
// async: false,
// defer: false,
// onLoaded: false,
// onError: false,
// fetchpriority: 'high'
// },
// {
// trigger: 'server'
// }
// )

useTideSiteMeta(props, nuxtApp?.$app_origin)
useTideFavicons(props.site, theme)
</script>
8 changes: 4 additions & 4 deletions packages/nuxt-ripple/composables/use-tide-favicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
const siteUrl = config?.siteUrl || ''
const themeColour = theme?.['rpl-clr-primary'] ?? '#ffffff'

const manifest = {

Check warning on line 11 in packages/nuxt-ripple/composables/use-tide-favicons.ts

View workflow job for this annotation

GitHub Actions / Test

'manifest' is assigned a value but never used. Allowed unused vars must match /props/u
id: siteUrl,
name: site?.name,
short_name: site?.shortName || '',
Expand All @@ -34,10 +34,10 @@
})
}

link.push({
rel: 'manifest',
href: `data:application/manifest+json,${encodeURIComponent(JSON.stringify(manifest))}`
})
// link.push({
// rel: 'manifest',
// href: `data:application/manifest+json,${encodeURIComponent(JSON.stringify(manifest))}`
// })

if (site.favicon?.src) {
link.push({
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt-ripple/composables/use-tide-hide-alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ try {
styleSheet.innerText = styles
document.head.appendChild(styleSheet)
}
console.log('Dismissed alerts')
} catch (e) {
console.error(e)
}`
Expand Down
35 changes: 35 additions & 0 deletions packages/nuxt-ripple/public/scripts/alerts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
;(function () {
console.log('alerts.js loaded')
function getCookie(name) {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
if (parts.length === 2) return parts.pop().split(';').shift()
}

const DISMISSED_ALERTS_COOKIE = 'dismissedAlerts'
const guidRegex = new RegExp(
'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4,5}-[0-9a-f]{4}-[0-9a-f]{12}$'
)

try {
const cookieValue = getCookie(DISMISSED_ALERTS_COOKIE)

if (cookieValue) {
const dismissedIds = JSON.parse(decodeURIComponent(cookieValue))

const styles = dismissedIds.reduce((result, id) => {
if (guidRegex.test(id)) {
return `${result} [data-alert-id="${id}"] {display: none;}`
}

return result
}, '')

const styleSheet = document.createElement('style')
styleSheet.innerText = styles
document.head.appendChild(styleSheet)
}
} catch (e) {
console.error(e)
}
})()
34 changes: 0 additions & 34 deletions packages/nuxt-ripple/server/plugins/prefetch.ts

This file was deleted.

40 changes: 40 additions & 0 deletions packages/ripple-sdp-core/layers/script-loader/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
modules: ['nuxt-security', '@nuxt/scripts'],
scripts: {
debug: true
},
security: {
rateLimiter: false,
strict: true, // Enables strict mode for security headers
nonce: true, // Enables HTML nonce support in SSR mode
headers: {
crossOriginEmbedderPolicy: 'unsafe-none',
crossOriginResourcePolicy: 'same-origin',
contentSecurityPolicy: {
'worker-src': ["'self'", 'blob:'],
'img-src': ["'self'", 'data:', process.env.NUXT_PUBLIC_TIDE_BASE_URL],
'style-src': ["'unsafe-inline'", "'self'"],
'font-src': [`'self'`, 'data:'],
'script-src': ["'nonce-{{nonce}}'", "'strict-dynamic'"],
'connect-src': ["'self'", 'https:']
}
}
},
routeRules: {
'/embed': {
security: {
headers: {
permissionsPolicy: {
'picture-in-picture': [],
geolocation: []
},
xFrameOptions: 'SAMEORIGIN',
crossOriginEmbedderPolicy: 'unsafe-none',
contentSecurityPolicy: false
}
}
}
}
})
110 changes: 110 additions & 0 deletions packages/ripple-sdp-core/layers/script-loader/pages/embed.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<template>
<div class="embed-container">
<div class="rpl-iframe">
<iframe
:src="url"
:height="height"
:title="oembed?.title"
:allow="allow"
:allowfullscreen="oembed?.allowfullscreen"
frameborder="0"
referrerpolicy="strict-origin-when-cross-origin"
class="rpl-iframe"
></iframe>
</div>
</div>
</template>

<script setup lang="ts">
import { useRoute } from '#imports'
import { extract } from '@extractus/oembed-extractor'

function extractSrcFromHtml(html: string): string | null {
// Regular expression to find the src attribute in an iframe
const iframeSrcRegex = /<iframe[^>]*\s+src="([^"]+)"/
const match = html.match(iframeSrcRegex)

// If a match is found, return the src URL, otherwise return null
return match ? match[1] : null
}

function extractAllowFromHtml(html: string): string | null {
// Regular expression to find the allow attribute in an iframe
const iframeAllowRegex = /<iframe[^>]*\s+allow="([^"]+)"/
const match = html.match(iframeAllowRegex)

// If a match is found, return the allow URL, otherwise return null
return match ? match[1] : null
}

const route = useRoute()
const ombedRef = ref(null)
const width = '100%'

Check warning on line 42 in packages/ripple-sdp-core/layers/script-loader/pages/embed.vue

View workflow job for this annotation

GitHub Actions / Test

'width' is assigned a value but never used. Allowed unused vars must match /props/u
const height = ref('800')
const updateHeight = () => {

Check warning on line 44 in packages/ripple-sdp-core/layers/script-loader/pages/embed.vue

View workflow job for this annotation

GitHub Actions / Test

'updateHeight' is assigned a value but never used. Allowed unused vars must match /props/u
if (window.innerHeight) {
height.value = `${window.innerHeight}px`
}
}

const iframeDomain = ref('')

let oembed = null
try {
oembed = await extract(route.query.url, { maxheight: 800 })
} catch (error) {
console.error('Failed to extract oembed:', error)
}

const allow = oembed && extractAllowFromHtml(oembed.html)
const iframePlayerSrc = oembed && extractSrcFromHtml(oembed.html)

if (oembed) {
ombedRef.value = oembed
} else {
ombedRef.value = {
html: '<div class="error-message">Invalid URL or unsupported domain.</div>'
}
}

const ALLOWED_DOMAINS = [
'vicgovgraduates.freshdesk.com',
'youtube.com',
'youtu.be',
'vimeo.com',
'google.com'
]

const url = computed(() => {
const urlString = iframePlayerSrc || route.query.url
if (!urlString) return null

try {
const urlObj = new URL(urlString)
iframeDomain.value = urlObj.hostname.replace('www.', '')
return ALLOWED_DOMAINS.some((allowed) =>
iframeDomain.value.endsWith(allowed)
)
? urlString
: null
} catch {
return null
}
})

defineRouteRules({
security: {
headers: {
permissionsPolicy: false
}
}
})
</script>

<style scoped>
.error-message {
padding: 1rem;
text-align: center;
color: #666;
}
</style>
40 changes: 40 additions & 0 deletions packages/ripple-sdp-core/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
experimental: {
inlineRouteRules: true
},
modules: ['nuxt-security', '@nuxt/scripts'],
scripts: {
debug: true
},
security: {
rateLimiter: false,
strict: true, // Enables strict mode for security headers
nonce: true, // Enables HTML nonce support in SSR mode
headers: {
crossOriginEmbedderPolicy: 'unsafe-none',
crossOriginResourcePolicy: 'same-origin',
contentSecurityPolicy: {
'worker-src': ["'self'", 'blob:'],
'img-src': ["'self'", 'data:', process.env.NUXT_PUBLIC_TIDE_BASE_URL],
'style-src': ["'unsafe-inline'", "'self'"],
'font-src': [`'self'`, 'data:'],
'script-src': ["'nonce-{{nonce}}'", "'strict-dynamic'"],
'connect-src': ["'self'", 'https:']
}
}
},
routeRules: {
'/embed': {
security: {
headers: {
permissionsPolicy: {
'picture-in-picture': [],
geolocation: []
},
xFrameOptions: 'SAMEORIGIN',
crossOriginEmbedderPolicy: 'unsafe-none',
contentSecurityPolicy: false
}
}
}
},
extends: [
'./layers/script-loader',
'@dpc-sdp/ripple-tide-event',
'@dpc-sdp/ripple-tide-topic',
'@dpc-sdp/ripple-tide-landing-page',
Expand Down
5 changes: 4 additions & 1 deletion packages/ripple-sdp-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"@dpc-sdp/ripple-tide-publication": "workspace:*",
"@dpc-sdp/ripple-tide-search": "workspace:*",
"@dpc-sdp/ripple-tide-topic": "workspace:*",
"@dpc-sdp/ripple-tide-webform": "workspace:*"
"@dpc-sdp/ripple-tide-webform": "workspace:*",
"@extractus/oembed-extractor": "^4.0.6",
"@nuxt/scripts": "^0.11.5",
"nuxt-security": "^2.2.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ const pluginEmbededVideo = function (this: any) {
const iframe = $video.find('iframe')
const height = iframe.attr('height')
const width = iframe.attr('width')
const source = iframe.attr('src')
const source = `/embed?url=${iframe.attr('src')}`
const title = $video.attr('title') || ''
const caption = $video.find('figcaption')?.text()
const link = $video.find('.field--name-field-media-link a')?.attr('href')
Expand Down Expand Up @@ -309,6 +309,10 @@ const pluginIFrames = function (this: any) {
const $iframe = this.find(el)
const wrapperClasses = ['rpl-iframe']

if (!$iframe.attr('src').startsWith('/embed?url')) {
$iframe.attr('src', `/embed?url=${$iframe.attr('src')}`)
}

// If no height setting from CMS, we give it a default height
if (!$iframe.attr('height')) {
wrapperClasses.push('rpl-iframe--default')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div class="rpl-iframe">
<iframe
:src="`/embed?url=${embedUrl}`"
frameborder="0"
class="rpl-iframe__content"
width="100%"
height="600"
allowfullscreen
></iframe>
</div>
</template>

<script setup lang="ts">
interface Props {
embedUrl: string
}

withDefaults(defineProps<Props>(), {})
</script>

<style></style>
Loading
Loading