Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ dist/lib/json/internalRelations.json
dist/lib/json/internalTypes.json
dist/lib/json/systemRelations.json
dist/lib/json/systemTypes.json

# local binaries
grpc-server
81 changes: 53 additions & 28 deletions src/ts/lib/api/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,42 +75,67 @@ class Dispatcher {
* Sets up listeners for data, status, and end events with automatic reconnection.
* Requires authentication token to be set in S.Auth.token.
*/
startStream () {
if (!S.Auth.token) {
console.error('[Dispatcher.startStream] No token');
return;
};
startStream (): Promise<void> {
return new Promise((resolve, reject) => {
if (!S.Auth.token) {
console.error('[Dispatcher.startStream] No token');
reject(new Error('No token'));
return;
};

window.clearTimeout(this.timeoutStream);
window.clearTimeout(this.timeoutStream);

this.stopStream();
this.stopStream();

this.stream = this.service.listenSessionEvents({ token: S.Auth.token }, null);
this.stream = this.service.listenSessionEvents({ token: S.Auth.token }, null);

this.stream.on('data', (event) => {
this.eventBuffer.push({ event, skipDebug: false });
let isResolved = false;
const finish = (source: string) => {
if (!isResolved) {
console.log(`[Dispatcher.startStream] Resolved by ${source}`);
isResolved = true;
resolve();
}
};

if (!this.flushScheduled) {
this.flushScheduled = true;
this.stream.on('metadata', () => finish('metadata'));

if (S.Common.isActiveTab) {
this.rafId = requestAnimationFrame(() => this.flushEvents());
} else {
this.flushTimerId = window.setTimeout(() => this.flushEvents(), 100);
// Fallback in case metadata never fires on grpc-web proxy
window.setTimeout(() => finish('timeout_fallback'), 150);

this.stream.on('data', (event) => {
finish('data');
this.eventBuffer.push({ event, skipDebug: false });

if (!this.flushScheduled) {
this.flushScheduled = true;

if (S.Common.isActiveTab) {
this.rafId = requestAnimationFrame(() => this.flushEvents());
} else {
this.flushTimerId = window.setTimeout(() => this.flushEvents(), 100);
};
};
};
});
});

this.stream.on('status', (status) => {
if (status.code) {
console.error('[Dispatcher.stream] Restarting', status);
this.reconnect();
};
});
this.stream.on('status', (status) => {
if (status.code !== 0) {
if (!isResolved) {
isResolved = true;
reject(new Error(`Stream status error: ${status.code}`));
}
console.error('[Dispatcher.stream] Restarting', status);
this.reconnect();
} else {
finish('status');
}
});

this.stream.on('end', () => {
console.error('[Dispatcher.stream] end, restarting');
this.reconnect();
this.stream.on('end', () => {
finish('end');
console.error('[Dispatcher.stream] end, restarting');
this.reconnect();
});
});
};

Expand Down Expand Up @@ -155,7 +180,7 @@ class Dispatcher {

window.clearTimeout(this.timeoutStream);
this.timeoutStream = window.setTimeout(() => {
this.startStream();
void this.startStream();
this.reconnects++;
}, t * 1000);
};
Expand Down
8 changes: 6 additions & 2 deletions src/ts/lib/util/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,16 @@ class UtilData {
*/
createSession(phrase: string, key: string, token: string, callBack?: (message: any) => void) {
this.closeSession(() => {
C.WalletCreateSession(phrase, key, token, (message: any) => {
C.WalletCreateSession(phrase, key, token, async (message: any) => {
if (!message.error.code) {
S.Auth.tokenSet(message.token);
S.Auth.appTokenSet(message.appToken);

dispatcher.startStream();
try {
await dispatcher.startStream();
} catch (err) {
console.error('[U.Data].createSession startStream failed', err);
};
};

callBack?.(message);
Expand Down
81 changes: 51 additions & 30 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export default defineConfig(({ mode }) => {
react(),
autoObserverPlugin(),
protobufCjsPlugin(),
devServerPlugin(),
spaFallbackPlugin(),

AutoImport({
imports: [
Expand Down Expand Up @@ -333,42 +333,63 @@ const mimeTypes: Record<string, string> = {
};

/**
* Dev server plugin: rewrite /index.html to /src/html/index.html for Vite processing,
* and serve static files from dist/ (tabs.html, workers, fonts, etc.) to match
* the old rspack devServer.static: ['dist'] behavior.
* Dev server plugin: rewrite /index.html to /src/html/index.html for Vite processing.
* It also intercepts root URL requests (like /tabs.html, /workers/..., /font/...) and
* attempts to serve them from dist/ first before falling back to the SPA index.html.
*/
function devServerPlugin(): Plugin {
function spaFallbackPlugin(): Plugin {
return {
name: 'dev-server-rewrites',
name: 'spa-fallback',
configureServer(server) {
// Serve static files from dist/ (tabs.html, workers/, font/, etc.)
server.middlewares.use((req, res, next) => {
if (!req.url) return next();
// Return function so this runs AFTER Vite's internal middleware
return () => {
server.middlewares.use(async (req, res, next) => {
const url = req.url || '';
const pathname = url.split('?')[0];

// Rewrite /index.html to /src/html/index.html so Vite processes it
if (req.url === '/index.html' || req.url === '/') {
req.url = '/src/html/index.html';
return next();
}
if (
pathname.startsWith('/@') ||
pathname.startsWith('/node_modules/') ||
pathname.startsWith('/src/') ||
pathname.startsWith('/dist/') ||
pathname.startsWith('/api/')
) {
return next();
}

// Try to serve from dist/ for static files (tabs.html, workers, fonts, etc.)
// Skip files that Vite should process through its pipeline (JS/TS modules)
const urlPath = req.url.split('?')[0];
if (urlPath.startsWith('/dist/lib/')) {
return next();
}
const distPath = path.resolve(__dirname, 'dist', urlPath.slice(1));
if (fs.existsSync(distPath) && fs.statSync(distPath).isFile()) {
const ext = path.extname(distPath).toLowerCase();
const contentType = mimeTypes[ext];
if (contentType) {
res.setHeader('Content-Type', contentType);
// Try to serve static assets from dist/ (tabs.html, workers, fonts, etc.)
const distPath = path.resolve(__dirname, 'dist', pathname.slice(1));
if (fs.existsSync(distPath) && fs.statSync(distPath).isFile()) {
const ext = path.extname(distPath).toLowerCase();
const contentType = mimeTypes[ext];
if (contentType) {
res.setHeader('Content-Type', contentType);
}
return res.end(fs.readFileSync(distPath));
}
return res.end(fs.readFileSync(distPath));
}

next();
});
const htmlPath = path.resolve(__dirname, 'src/html/index.html');
if (!fs.existsSync(htmlPath)) {
return next();
}

try {
const raw = fs.readFileSync(htmlPath, 'utf-8');
const html = await server.transformIndexHtml(
'/src/html/index.html',
raw,
req.originalUrl
);

res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(html);
} catch (err) {
console.error('[SPA Fallback] Error:', err);
next(err);
}
});
};
},
};
}
3 changes: 2 additions & 1 deletion vite.web.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ export default defineConfig(({ mode }) => {
'dist/lib/pb/protos/commands_pb.js',
'dist/lib/pb/protos/events_pb.js',
'dist/lib/pkg/lib/pb/model/protos/models_pb.js',
'dist/lib/pb/protos/service/service_grpc_web_pb.js',
'google-protobuf',
'grpc-web',
],
Expand Down Expand Up @@ -336,7 +337,7 @@ export default defineConfig(({ mode }) => {
html = html.replace(/(?:\.\.\/)+(?=js\/|css\/|assets\/)/g, '/');
fs.writeFileSync(dest, html);
fs.unlinkSync(src);
try { fs.rmdirSync(path.resolve(__dirname, 'dist-web/dist')); } catch {}
try { fs.rmdirSync(path.resolve(__dirname, 'dist-web/dist')); } catch (e) { console.error('[move-html] Failed to remove dist-web/dist:', e); }
}
},
} as Plugin,
Expand Down
Loading