@igoruehara/canvas-flow 0.1.10 → 0.1.11

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.
@@ -465,6 +465,19 @@ function joinCorsOrigins(config, publicUrl, port) {
465
465
  ].join(',');
466
466
  }
467
467
 
468
+ function isLoopbackUrl(value) {
469
+ try {
470
+ const hostname = new URL(value).hostname.toLowerCase();
471
+ return hostname === 'localhost'
472
+ || hostname === '::1'
473
+ || hostname === '[::1]'
474
+ || hostname === '0.0.0.0'
475
+ || hostname.startsWith('127.');
476
+ } catch {
477
+ return false;
478
+ }
479
+ }
480
+
468
481
  function applyEnvironment(config, paths, flags) {
469
482
  const port = Number(flags.port || config.server.port || 3333);
470
483
  const publicUrl = String(flags['public-url'] || config.server.publicUrl || `http://localhost:${port}`).replace(/\/$/, '');
@@ -515,11 +528,18 @@ function applyEnvironment(config, paths, flags) {
515
528
  setEnv('MONGO_SERVER_SELECTION_TIMEOUT_MS', config.database.mongoServerSelectionTimeoutMs);
516
529
  setEnv('MONGO_CONNECT_TIMEOUT_MS', config.database.mongoConnectTimeoutMs);
517
530
 
518
- setBoolEnv('CANVAS_FLOW_LOGIN', asBool(config.auth.login));
531
+ const loginRequired = asBool(config.auth.login);
532
+ const exposeApiTokenToFrontend = config.auth.exposeApiTokenToFrontend === true
533
+ || (!loginRequired && isLoopbackUrl(publicUrl));
534
+ setBoolEnv('CANVAS_FLOW_LOGIN', loginRequired);
519
535
  setEnv('CANVAS_FLOW_LOGIN_TTL_HOURS', config.auth.loginTtlHours);
520
536
  setEnv('CANVAS_FLOW_LOGIN_THROTTLE_WINDOW_MS', config.auth.loginThrottleWindowMs || 600000);
521
537
  setEnv('CANVAS_FLOW_LOGIN_MAX_ATTEMPTS', config.auth.loginMaxAttempts || 8);
522
538
  setEnv('CANVAS_FLOW_API_TOKEN', config.auth.apiToken);
539
+ delete process.env.CANVAS_FLOW_FRONTEND_API_TOKEN;
540
+ if (!loginRequired && exposeApiTokenToFrontend) {
541
+ setEnv('CANVAS_FLOW_FRONTEND_API_TOKEN', config.auth.apiToken);
542
+ }
523
543
  setEnv('CANVAS_FLOW_JWT_SECRET', config.auth.jwtSecret);
524
544
  setEnv('CANVAS_FLOW_MEDIA_PROXY_SECRET', config.auth.mediaProxySecret);
525
545
  setEnv('CANVAS_FLOW_MEDIA_PROXY_TTL_SECONDS', config.auth.mediaProxyTtlSeconds);
@@ -815,6 +835,59 @@ function sleep(ms) {
815
835
  return new Promise((resolve) => setTimeout(resolve, ms));
816
836
  }
817
837
 
838
+ async function checkHttpOk(url, timeoutMs = 1200) {
839
+ const controller = new AbortController();
840
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
841
+ try {
842
+ const response = await fetch(url, { signal: controller.signal });
843
+ return response.ok;
844
+ } catch {
845
+ return false;
846
+ } finally {
847
+ clearTimeout(timer);
848
+ }
849
+ }
850
+
851
+ function startStartupStatus(publicUrl, options = {}) {
852
+ const healthUrl = `${String(publicUrl || '').replace(/\/$/, '')}/health`;
853
+ const startedAt = Date.now();
854
+ let stopped = false;
855
+
856
+ console.log('Canvas Flow startup: loading application bundle...');
857
+ console.log(`Canvas Flow startup: waiting for backend health at ${healthUrl}`);
858
+
859
+ const interval = setInterval(() => {
860
+ const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
861
+ console.log(`Canvas Flow startup: still starting (${elapsedSeconds}s elapsed)...`);
862
+ }, 2500);
863
+
864
+ const stop = () => {
865
+ if (stopped) return;
866
+ stopped = true;
867
+ clearInterval(interval);
868
+ };
869
+
870
+ void (async () => {
871
+ const deadline = Date.now() + 90000;
872
+ while (!stopped && Date.now() < deadline) {
873
+ if (await checkHttpOk(healthUrl)) {
874
+ stop();
875
+ console.log(`Canvas Flow ready: ${publicUrl}`);
876
+ if (options.openBrowser) openBrowser(publicUrl);
877
+ return;
878
+ }
879
+ await sleep(500);
880
+ }
881
+
882
+ if (!stopped) {
883
+ stop();
884
+ console.log('Canvas Flow startup: health check is still pending. Keep this terminal open and watch the backend logs above.');
885
+ }
886
+ })();
887
+
888
+ return { stop };
889
+ }
890
+
818
891
  function mongoConnectionOptions(config) {
819
892
  return {
820
893
  serverSelectionTimeoutMS: Number(config.database?.mongoServerSelectionTimeoutMs || 8000),
@@ -1147,12 +1220,13 @@ async function start(flags) {
1147
1220
  }
1148
1221
 
1149
1222
  const shouldOpen = flags.open === true || (flags.open !== false && config.server.openBrowser === true);
1150
- if (shouldOpen) {
1151
- const timer = setTimeout(() => openBrowser(runtime.publicUrl), 1200);
1152
- if (typeof timer.unref === 'function') timer.unref();
1223
+ const startupStatus = startStartupStatus(runtime.publicUrl, { openBrowser: shouldOpen });
1224
+ try {
1225
+ require(SERVER_ENTRY);
1226
+ } catch (error) {
1227
+ startupStatus.stop();
1228
+ throw error;
1153
1229
  }
1154
-
1155
- require(SERVER_ENTRY);
1156
1230
  }
1157
1231
 
1158
1232
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igoruehara/canvas-flow",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Standalone npm launcher for Canvas Flow multi-agent GenAI workflows.",
5
5
  "homepage": "https://github.com/igoruehara/canvas-flow#readme",
6
6
  "repository": {