app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
aps.py+28−5@@ -287,16 +287,25 @@ def _open_os_window_extension_free(url: str, profile: str = None) -> dict: profile = None a = {"sessionId": "aps-signin-%d" % int(time.time()), "thread": "fusion-aps-signin",- "purpose": "Autodesk APS consent (extension-free)",+ "purpose": "Autodesk APS consent", "url": url, "background": True} if profile: a["profile"] = profile- r = _ad("nbrowser_open_os_window", a, timeout=60)+ # PREFER the extension window: nbrowser_open_window makes the tab AGENT-OWNED, and CDP only+ # drives tabs it owns (nbrowser_* refuses `not_owned` with no override). Owning it is what+ # lets us CLICK 'Allow' for the user instead of leaving a consent page sitting there - the+ # same pattern that finally completed the Fusion sign-in. Fall back to the extension-free+ # OS window when there is no extension (issue #12: it must still work with none installed).+ r = _ad("nbrowser_open_window", a, timeout=60)+ owned = isinstance(r, dict) and not r.get("_adCallError") and (r.get("ok") or r.get("sessionId"))+ if not owned:+ r = _ad("nbrowser_open_os_window", a, timeout=60) if not isinstance(r, dict) or r.get("_adCallError"): return {"launched": False, "profile": profile, "error": (r or {}).get("_adCallError") or "nbrowser_open_os_window unavailable"}- launched = bool(r.get("ok") or r.get("hwnd") or "Launch fired" in str(r.get("_hint", "")))- return {"launched": launched,+ launched = bool(r.get("ok") or r.get("hwnd") or r.get("sessionId")+ or "Launch fired" in str(r.get("_hint", "")))+ return {"launched": launched, "agentOwned": bool(owned), "where": r.get("displayName") or r.get("profile") or profile, "profile": r.get("profile") or profile, "hwnd": r.get("hwnd"),@@ -725,13 +734,27 @@ def handle_status(args: dict) -> dict: tok = _load_tokens() has_at = bool(tok.get("access_token")) live = has_at and int(tok.get("expires_at", 0)) > int(time.time())+ # signedIn MUST mean "I can actually call APS right now", not "a token file exists".+ # It used to be bool(has_at), so status happily reported signedIn:true while every search+ # died with APS token_expired - and fusion_readiness repeated that lie to the user+ # (caught live 2026-07-22). _valid_access_token() refreshes when possible, so this is the+ # honest signal: usable == live now, or refreshable now.+ usable = bool(live)+ if has_at and not live:+ try:+ usable = bool(_valid_access_token())+ except Exception:+ usable = False+ refresh_failed = bool(has_at and not usable) with _signin_lock: pending = bool(_signin_state and not _signin_state["done"] and time.time() - _signin_state["startedAt"] < 180) usage = _read_usage() data = { "configured": bool(client_id),- "signedIn": bool(has_at),+ "signedIn": bool(usable), # usable NOW (refreshed if needed), not merely present+ "hasStoredToken": bool(has_at), # a token file exists (may be dead)+ "refreshFailed": refresh_failed, # stored token is dead and could not be refreshed "tokenLive": bool(live), "tokenExpiresAt": tok.get("expires_at"), "scope": tok.get("scope", SCOPES),