Skip to content
Open
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
323 changes: 75 additions & 248 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions starters/hub-api-ts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vercel
55 changes: 29 additions & 26 deletions starters/hub-api-ts/src/tests/hub-api.browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ it('opens Hub popup and verifies it loads the Hub interface', async () => {

// Override window.open to capture the popup reference
const originalOpen = window.open
window.open = function (...args: Parameters<typeof window.open>) {
window.open = function (...args: Parameters<typeof window.open>): Window | null {
const url = args[0]?.toString() || ''

// Check if it's the Hub
Expand All @@ -41,12 +41,12 @@ it('opens Hub popup and verifies it loads the Hub interface', async () => {
hubPopupUrl = url

// Actually open the popup so we can inspect it
hubPopup = originalOpen.apply(window, args)
hubPopup = originalOpen.apply(window, args) as Window | null
return hubPopup
}

return originalOpen.apply(window, args)
} as typeof window.open
return originalOpen.apply(window, args) as Window | null
}

// Import and initialize the app
await import('../main')
Expand All @@ -65,29 +65,32 @@ it('opens Hub popup and verifies it loads the Hub interface', async () => {
expect(hubPopup).toBeTruthy()

// Try to verify the popup loaded (if accessible due to CORS/same-origin policy)
if (hubPopup && !hubPopup.closed) {
try {
// Wait a bit for the Hub to load
await sleep(3000)

// Check if we can access the popup's location
const popupLocation = hubPopup.location.href
expect(popupLocation).toContain('hub.nimiq')
console.log('✅ Hub popup verified - URL:', popupLocation)

// Close the popup
hubPopup.close()
}
catch (error) {
// If we get a cross-origin error, that's actually good - it means the real Hub loaded
const errorMessage = error instanceof Error ? error.message : String(error)
if (errorMessage.includes('cross-origin') || errorMessage.includes('SecurityError')) {
console.log('✅ Hub popup loaded (cross-origin - real Hub domain)')
console.log(' Expected cross-origin restriction confirms real Hub loaded')
hubPopup.close()
if (hubPopup) {
const popup = hubPopup as Window
if (!popup.closed) {
try {
// Wait a bit for the Hub to load
await sleep(3000)

// Check if we can access the popup's location
const popupLocation = popup.location.href
expect(popupLocation).toContain('hub.nimiq')
console.log('✅ Hub popup verified - URL:', popupLocation)

// Close the popup
popup.close()
}
else {
throw error
catch (error) {
// If we get a cross-origin error, that's actually good - it means the real Hub loaded
const errorMessage = error instanceof Error ? error.message : String(error)
if (errorMessage.includes('cross-origin') || errorMessage.includes('SecurityError')) {
console.log('✅ Hub popup loaded (cross-origin - real Hub domain)')
console.log(' Expected cross-origin restriction confirms real Hub loaded')
popup.close()
}
else {
throw error
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion starters/react-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"playwright": "^1.55.1",
"typescript": "^5.9.2",
"vite": "^7.1.7",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0",
"vitest": "^3.2.4"
}
Expand Down
90 changes: 56 additions & 34 deletions starters/react-ts/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Plugin } from 'vite'
import { copyFile, cp, mkdir } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath, URL } from 'node:url'

import { fileURLToPath, URL } from 'node:url'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import topLevelAwait from 'vite-plugin-top-level-await'
import wasm from 'vite-plugin-wasm'

function copyComlinkPlugin() {
function copyComlinkPlugin(): Plugin {
return {
name: 'copy-comlink-worker-dependency',
apply: 'build',
Expand All @@ -21,57 +21,79 @@ function copyComlinkPlugin() {
await mkdir(distAssetsDir, { recursive: true })
await copyFile(src, dest)

// Copy worker WASM files if they exist
const workerSrcDir = path.join(distDir, 'worker-wasm')
const workerDestDir = path.join(distAssetsDir, 'worker-wasm')
await cp(workerSrcDir, workerDestDir, { recursive: true })
try {
await cp(workerSrcDir, workerDestDir, { recursive: true })
}
catch (error) {
// Ignore if worker-wasm doesn't exist
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error
}
}
},
}
}

/**
* Vite plugin for Nimiq blockchain integration
* Configures WebAssembly support and optimizations required for @nimiq/core
*
* Note: This plugin does not include top-level await support. Modern browsers
* support top-level await natively. If you need support for older browsers,
* you can add vite-plugin-top-level-await to your plugins array manually.
*
* @param {object} [options] - Plugin options
* @param {boolean} [options.worker] - Configure worker support for WASM
* @returns {import('vite').Plugin[]} Array of Vite plugins
*/
function nimiq({ worker = true } = {}) {
return [
wasm(),
{
name: 'vite-plugin-nimiq',
config() {
return {
optimizeDeps: {
exclude: ['@nimiq/core'],
},
build: {
target: 'esnext',
rollupOptions: {
output: {
format: 'es',
},
},
},
...(worker && {
worker: {
format: 'es',
plugins: [wasm()],
},
}),
}
},
},
]
}

// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
wasm(),
topLevelAwait({
// The module that contains the top-level await
promiseExportName: '__tla',
// The function to generate the promise export name
promiseImportName: i => `__tla_${i}`,
}),
nimiq(),
copyComlinkPlugin(),
],
worker: {
format: 'es',
rollupOptions: {
output: {
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
},
},
plugins: () => [
wasm(),
topLevelAwait({
promiseExportName: '__tla',
promiseImportName: i => `__tla_${i}`,
}),
],
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
optimizeDeps: {
exclude: ['@nimiq/core'],
},
// Additional build configuration for Nimiq
build: {
target: 'esnext',
rollupOptions: {
output: {
format: 'es',
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
Expand Down
1 change: 0 additions & 1 deletion starters/vue-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"playwright": "^1.55.1",
"typescript": "^5.9.2",
"vite": "^7.1.7",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-vue-devtools": "^8.0.0",
"vite-plugin-wasm": "^3.5.0",
"vitest": "^3.2.4",
Expand Down
72 changes: 56 additions & 16 deletions starters/vue-ts/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { Plugin } from 'vite'
import { copyFile, cp, mkdir } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath, URL } from 'node:url'

import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import topLevelAwait from 'vite-plugin-top-level-await'
import vueDevTools from 'vite-plugin-vue-devtools'
import wasm from 'vite-plugin-wasm'

function copyComlinkPlugin() {
function copyComlinkPlugin(): Plugin {
return {
name: 'copy-comlink-worker-dependency',
apply: 'build',
Expand All @@ -22,35 +22,75 @@ function copyComlinkPlugin() {
await mkdir(distAssetsDir, { recursive: true })
await copyFile(src, dest)

// Copy worker WASM files if they exist
const workerSrcDir = path.join(distDir, 'worker-wasm')
const workerDestDir = path.join(distAssetsDir, 'worker-wasm')
await cp(workerSrcDir, workerDestDir, { recursive: true })
try {
await cp(workerSrcDir, workerDestDir, { recursive: true })
}
catch (error) {
// Ignore if worker-wasm doesn't exist
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error
}
}
},
}
}

/**
* Vite plugin for Nimiq blockchain integration
* Configures WebAssembly support and optimizations required for @nimiq/core
*
* Note: This plugin does not include top-level await support. Modern browsers
* support top-level await natively. If you need support for older browsers,
* you can add vite-plugin-top-level-await to your plugins array manually.
*
* @param {object} [options] - Plugin options
* @param {boolean} [options.worker] - Configure worker support for WASM
* @returns {import('vite').Plugin[]} Array of Vite plugins
*/
function nimiq({ worker = true } = {}) {
return [
wasm(),
{
name: 'vite-plugin-nimiq',
config() {
return {
optimizeDeps: {
exclude: ['@nimiq/core'],
},
build: {
target: 'esnext',
rollupOptions: {
output: {
format: 'es',
},
},
},
...(worker && {
worker: {
format: 'es',
plugins: [wasm()],
},
}),
}
},
},
]
}

// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
wasm(),
topLevelAwait(),
nimiq(),
copyComlinkPlugin(),
],
worker: {
format: 'es',
plugins: () => [
wasm(),
topLevelAwait(),
],
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
optimizeDeps: {
exclude: ['@nimiq/core'],
},
})