Refactor Indeehub integration and enhance deployment documentation
- Updated Indeehub references throughout the codebase, changing the name from "IndeedHub" to "Indeehub" for consistency. - Implemented a virtual app structure for Indeehub, allowing it to open an external URL without requiring a container. - Enhanced deployment scripts and documentation to clarify SSH access and password management for Indeehub. - Improved error handling and retry logic in various components to ensure better user experience during onboarding and app interactions. - Updated CSS for visual enhancements and added new buttons for improved navigation in the AppLauncherOverlay.
This commit is contained in:
@@ -6,10 +6,18 @@ let audioContext: AudioContext | null = null
|
||||
let introAudio: HTMLAudioElement | null = null
|
||||
let introGain: GainNode | null = null
|
||||
|
||||
/** Get AudioContext - only returns existing. Create via resumeAudioContext() after user gesture. */
|
||||
function getContext(): AudioContext | null {
|
||||
return audioContext
|
||||
}
|
||||
|
||||
/** Create AudioContext if needed (call only from user gesture - click/tap/key) */
|
||||
function ensureContext(): AudioContext | null {
|
||||
if (audioContext) return audioContext
|
||||
try {
|
||||
audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
|
||||
const Ctx = window.AudioContext || (window as any).webkitAudioContext
|
||||
if (!Ctx) return null
|
||||
audioContext = new Ctx()
|
||||
return audioContext
|
||||
} catch {
|
||||
return null
|
||||
@@ -44,21 +52,21 @@ export function playLoopStart() {
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
/** Resume audio context (call on first user interaction to unlock autoplay) */
|
||||
/** Resume audio context - MUST be called from user gesture (click/tap/key). Creates context if needed. */
|
||||
export function resumeAudioContext() {
|
||||
const ctx = getContext()
|
||||
const ctx = ensureContext()
|
||||
if (ctx?.state === 'suspended') {
|
||||
ctx.resume().catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
/** Start intro loop - Cosmic Updrift (free royalty) */
|
||||
/** Start intro loop - Cosmic Updrift. Only works after resumeAudioContext() (user gesture). */
|
||||
export function startSynthwave() {
|
||||
const ctxOrNull = getContext()
|
||||
if (!ctxOrNull) return
|
||||
|
||||
try {
|
||||
if (ctxOrNull.state === 'suspended') ctxOrNull.resume()
|
||||
if (ctxOrNull.state === 'suspended') ctxOrNull.resume().catch(() => {})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
/**
|
||||
* Onboarding state - prefers backend, falls back to localStorage for mock/offline.
|
||||
* Hardened: retries on 502/503, never blocks completion.
|
||||
*/
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
export async function isOnboardingComplete(): Promise<boolean> {
|
||||
try {
|
||||
return await rpcClient.isOnboardingComplete()
|
||||
} catch {
|
||||
return localStorage.getItem('neode_onboarding_complete') === '1'
|
||||
async function callWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T | null> {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : ''
|
||||
const isRetryable = /502|503|timeout|fetch|network/i.test(msg)
|
||||
if (!isRetryable || i === maxRetries - 1) return null
|
||||
await new Promise((r) => setTimeout(r, 800 * (i + 1)))
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function isOnboardingComplete(): Promise<boolean> {
|
||||
const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2)
|
||||
if (result !== null) return result
|
||||
return localStorage.getItem('neode_onboarding_complete') === '1'
|
||||
}
|
||||
|
||||
export async function completeOnboarding(): Promise<void> {
|
||||
try {
|
||||
await rpcClient.completeOnboarding()
|
||||
} finally {
|
||||
localStorage.setItem('neode_onboarding_complete', '1')
|
||||
}
|
||||
await callWithRetry(() => rpcClient.completeOnboarding(), 3)
|
||||
localStorage.setItem('neode_onboarding_complete', '1')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user