botschat 0.1.20 → 0.1.21
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.
- package/package.json +1 -1
- package/packages/api/src/do/connection-do.ts +186 -382
- package/packages/api/src/index.ts +50 -67
- package/packages/api/src/routes/agents.ts +3 -3
- package/packages/api/src/routes/auth.ts +1 -0
- package/packages/api/src/routes/channels.ts +11 -11
- package/packages/api/src/routes/demo.ts +156 -0
- package/packages/api/src/routes/sessions.ts +5 -5
- package/packages/api/src/routes/tasks.ts +33 -33
- package/packages/plugin/dist/src/channel.js +50 -0
- package/packages/plugin/dist/src/channel.js.map +1 -1
- package/packages/plugin/package.json +18 -2
- package/packages/web/dist/assets/index-BtPyCBCl.css +1 -0
- package/packages/web/dist/assets/index-BtpsFe4Z.js +2 -0
- package/packages/web/dist/assets/index-CQbIYr6_.js +2 -0
- package/packages/web/dist/assets/{index-CYQMu_-c.js → index-C_GamcQc.js} +1 -1
- package/packages/web/dist/assets/index-LiBjPMg2.js +1 -0
- package/packages/web/dist/assets/{index-DYCO-ry1.js → index-MyoWvQAH.js} +1 -1
- package/packages/web/dist/assets/index-STIPTMK8.js +1516 -0
- package/packages/web/dist/assets/{index.esm-CvOpngZM.js → index.esm-BpQAwtdR.js} +1 -1
- package/packages/web/dist/assets/{web-D3LMODYp.js → web-BbTzVNLt.js} +1 -1
- package/packages/web/dist/assets/{web-1cdhq2RW.js → web-cnzjgNfD.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/src/App.tsx +9 -56
- package/packages/web/src/api.ts +5 -61
- package/packages/web/src/components/ChatWindow.tsx +9 -9
- package/packages/web/src/components/CronDetail.tsx +1 -1
- package/packages/web/src/components/ImageLightbox.tsx +96 -0
- package/packages/web/src/components/LoginPage.tsx +59 -1
- package/packages/web/src/components/MessageContent.tsx +17 -2
- package/packages/web/src/components/SessionTabs.tsx +1 -1
- package/packages/web/src/components/Sidebar.tsx +1 -3
- package/packages/web/src/hooks/useIMEComposition.ts +14 -9
- package/packages/web/src/store.ts +7 -39
- package/packages/web/src/ws.ts +0 -1
- package/scripts/dev.sh +0 -53
- package/migrations/0013_agents_table.sql +0 -29
- package/migrations/0014_agent_sessions.sql +0 -19
- package/migrations/0015_message_traces.sql +0 -27
- package/migrations/0016_multi_agent_channels_messages.sql +0 -9
- package/migrations/0017_rename_cron_job_id.sql +0 -2
- package/packages/api/src/protocol-v2.ts +0 -154
- package/packages/api/src/routes/agents-v2.ts +0 -192
- package/packages/api/src/routes/history-v2.ts +0 -221
- package/packages/api/src/routes/migrate-v2.ts +0 -110
- package/packages/web/dist/assets/index-BARPtt0v.css +0 -1
- package/packages/web/dist/assets/index-Bf-XL3te.js +0 -2
- package/packages/web/dist/assets/index-CYlvfpX9.js +0 -1519
- package/packages/web/dist/assets/index-CxcpA4Qo.js +0 -1
- package/packages/web/dist/assets/index-QebPVqwj.js +0 -2
- package/packages/web/src/components/AgentSettings.tsx +0 -328
- package/scripts/mock-openclaw-v2.mjs +0 -486
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as b,_ as m,C as y,b as D,E as X,o as R,F as Ae,g as k,c as ve,v as Ee,i as _e,d as Ce,e as A}from"./index-
|
|
1
|
+
import{a as b,_ as m,C as y,b as D,E as X,o as R,F as Ae,g as k,c as ve,v as Ee,i as _e,d as Ce,e as A}from"./index-STIPTMK8.js";const Q="@firebase/installations",M="0.6.19";/**
|
|
2
2
|
* @license
|
|
3
3
|
* Copyright 2019 Google LLC
|
|
4
4
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as j}from"./index-CYlvfpX9.js";class T extends j{constructor(){super()}parseJwt(e){const t=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),r=decodeURIComponent(atob(t).split("").map(n=>"%"+("00"+n.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(r)}async loadScript(e){return new Promise((o,t)=>{const r=document.createElement("script");r.src=e,r.async=!0,r.onload=()=>{o()},r.onerror=t,document.body.appendChild(r)})}}T.OAUTH_STATE_KEY="social_login_oauth_pending";class B extends T{constructor(){super(...arguments),this.clientId=null,this.redirectUrl=null,this.scriptLoaded=!1,this.scriptUrl="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js",this.useProperTokenExchange=!1}async initialize(e,o,t=!1){this.clientId=e,this.redirectUrl=o||null,this.useProperTokenExchange=t,e&&await this.loadAppleScript()}async login(e){if(!this.clientId)throw new Error("Apple Client ID not set. Call initialize() first.");if(!this.scriptLoaded)throw new Error("Apple Sign-In script not loaded.");return new Promise((o,t)=>{var r,n;AppleID.auth.init({clientId:(r=this.clientId)!==null&&r!==void 0?r:"",scope:((n=e.scopes)===null||n===void 0?void 0:n.join(" "))||"name email",redirectURI:this.redirectUrl||window.location.href,state:e.state,nonce:e.nonce,usePopup:!0}),AppleID.auth.signIn().then(i=>{var a,s,l,c,h;let u=null;this.useProperTokenExchange?u=null:u={token:i.authorization.code||""};const p=Object.assign({profile:{user:i.user||"",email:((a=i.user)===null||a===void 0?void 0:a.email)||null,givenName:((l=(s=i.user)===null||s===void 0?void 0:s.name)===null||l===void 0?void 0:l.firstName)||null,familyName:((h=(c=i.user)===null||c===void 0?void 0:c.name)===null||h===void 0?void 0:h.lastName)||null},accessToken:u,idToken:i.authorization.id_token||null},this.useProperTokenExchange&&{authorizationCode:i.authorization.code});o({provider:"apple",result:p})}).catch(i=>{t(i)})})}async logout(){console.log("Apple logout: Session should be managed on the client side")}async isLoggedIn(){return console.log("Apple login status should be managed on the client side"),{isLoggedIn:!1}}async getAuthorizationCode(){throw console.log("Apple authorization code should be stored during login"),new Error("Apple authorization code not available")}async refresh(){console.log("Apple refresh not available on web")}async loadAppleScript(){if(!this.scriptLoaded)return this.loadScript(this.scriptUrl).then(()=>{this.scriptLoaded=!0})}}class Y extends T{constructor(){super(...arguments),this.appId=null,this.scriptLoaded=!1,this.locale="en_US"}async initialize(e,o){this.appId=e,o&&(this.locale=o),e&&(await this.loadFacebookScript(this.locale),FB.init({appId:this.appId,version:"v17.0",xfbml:!0,cookie:!0}))}async login(e){if(!this.appId)throw new Error("Facebook App ID not set. Call initialize() first.");return new Promise((o,t)=>{FB.login(r=>{r.status==="connected"?FB.api("/me",{fields:"id,name,email,picture"},n=>{var i,a;const s={accessToken:{token:r.authResponse.accessToken,userId:r.authResponse.userID},profile:{userID:n.id,name:n.name,email:n.email||null,imageURL:((a=(i=n.picture)===null||i===void 0?void 0:i.data)===null||a===void 0?void 0:a.url)||null,friendIDs:[],birthday:null,ageRange:null,gender:null,location:null,hometown:null,profileURL:null},idToken:null};o({provider:"facebook",result:s})}):t(new Error("Facebook login failed"))},{scope:e.permissions.join(",")})})}async logout(){return new Promise(e=>{FB.logout(()=>e())})}async isLoggedIn(){return new Promise(e=>{FB.getLoginStatus(o=>{e({isLoggedIn:o.status==="connected"})})})}async getAuthorizationCode(){return new Promise((e,o)=>{FB.getLoginStatus(t=>{var r;t.status==="connected"?e({accessToken:((r=t.authResponse)===null||r===void 0?void 0:r.accessToken)||""}):o(new Error("No Facebook authorization code available"))})})}async refresh(e){await this.login(e)}async loadFacebookScript(e){if(this.scriptLoaded)return;const o=document.querySelector('script[src*="connect.facebook.net"]');return o&&o.remove(),this.loadScript(`https://connect.facebook.net/${e}/sdk.js`).then(()=>{this.scriptLoaded=!0})}}class V extends T{constructor(){super(...arguments),this.clientId=null,this.loginType="online",this.GOOGLE_TOKEN_REQUEST_URL="https://www.googleapis.com/oauth2/v3/tokeninfo",this.GOOGLE_STATE_KEY="capgo_social_login_google_state"}async initialize(e,o,t,r){this.clientId=e,o&&(this.loginType=o),this.hostedDomain=t,this.redirectUrl=r}async login(e){if(!this.clientId)throw new Error("Google Client ID not set. Call initialize() first.");let o=e.scopes||[];o.length>0?(o.includes("https://www.googleapis.com/auth/userinfo.email")||o.push("https://www.googleapis.com/auth/userinfo.email"),o.includes("https://www.googleapis.com/auth/userinfo.profile")||o.push("https://www.googleapis.com/auth/userinfo.profile"),o.includes("openid")||o.push("openid")):o=["https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/userinfo.profile","openid"];const t=e.nonce||Math.random().toString(36).substring(2);return this.traditionalOAuth({scopes:o,nonce:t,hostedDomain:this.hostedDomain,prompt:e.prompt})}async logout(){if(this.loginType==="offline")return Promise.reject("Offline login doesn't store tokens. logout is not available");const e=this.getGoogleState();e&&await this.rawLogoutGoogle(e.accessToken)}async isLoggedIn(){if(this.loginType==="offline")return Promise.reject("Offline login doesn't store tokens. isLoggedIn is not available");const e=this.getGoogleState();if(!e)return{isLoggedIn:!1};try{const o=await this.accessTokenIsValid(e.accessToken),t=this.idTokenValid(e.idToken);if(o&&t)return{isLoggedIn:!0};try{await this.rawLogoutGoogle(e.accessToken,!1)}catch(r){console.error("Access token is not valid, but cannot logout",r)}return{isLoggedIn:!1}}catch(o){return Promise.reject(o)}}async getAuthorizationCode(){if(this.loginType==="offline")return Promise.reject("Offline login doesn't store tokens. getAuthorizationCode is not available");const e=this.getGoogleState();if(!e)throw new Error("No Google authorization code available");try{const o=await this.accessTokenIsValid(e.accessToken),t=this.idTokenValid(e.idToken);if(o&&t)return{accessToken:e.accessToken,jwt:e.idToken};try{await this.rawLogoutGoogle(e.accessToken,!1)}catch(r){console.error("Access token is not valid, but cannot logout",r)}throw new Error("No Google authorization code available")}catch(o){return Promise.reject(o)}}async refresh(){return Promise.reject("Not implemented")}handleOAuthRedirect(e){const o=e.searchParams,t=o.get("error");if(t)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:o.get("error_description")||t};const r=o.get("code");if(r&&o.has("scope"))return{provider:"google",result:{serverAuthCode:r,responseType:"offline"}};const n=e.hash.substring(1);if(console.log("handleOAuthRedirect",e.hash),!n)return null;const i=new URLSearchParams(n),a=i.get("error");if(a)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:i.get("error_description")||a};console.log("handleOAuthRedirect ok");const s=i.get("access_token"),l=i.get("id_token");if(s&&l){localStorage.removeItem(T.OAUTH_STATE_KEY);const c=this.parseJwt(l);return{provider:"google",result:{accessToken:{token:s},idToken:l,profile:{email:c.email||null,familyName:c.family_name||null,givenName:c.given_name||null,id:c.sub||null,name:c.name||null,imageUrl:c.picture||null},responseType:"online"}}}return null}async accessTokenIsValid(e){const o=`${this.GOOGLE_TOKEN_REQUEST_URL}?access_token=${encodeURIComponent(e)}`;try{const t=await fetch(o);if(!t.ok)return console.log(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response not successful. Status code: ${t.status}. Assuming that the token is not valid`),!1;const r=await t.text();if(!r)throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`);let n;try{n=JSON.parse(r)}catch(s){throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${s}`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${s}`)}const i=n.expires_in;if(i==null)throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`);let a;try{if(a=parseInt(i,10),isNaN(a))throw new Error("'expires_in' is not a valid integer")}catch(s){throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${i} is not a valid integer. Error: ${s}`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${i} is not a valid integer. Error: ${s}`)}return a>5}catch(t){throw console.error(t),t}}idTokenValid(e){try{const o=this.parseJwt(e),t=Math.ceil(Date.now()/1e3)+5;return o.exp&&t<o.exp}catch{return!1}}async rawLogoutGoogle(e,o=null){if(o===null&&(o=await this.accessTokenIsValid(e)),o===!0){try{await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${encodeURIComponent(e)}`),this.clearStateGoogle()}catch{}return}else{this.clearStateGoogle();return}}persistStateGoogle(e,o){try{window.localStorage.setItem(this.GOOGLE_STATE_KEY,JSON.stringify({accessToken:e,idToken:o}))}catch(t){console.error("Cannot persist state google",t)}}clearStateGoogle(){try{window.localStorage.removeItem(this.GOOGLE_STATE_KEY)}catch(e){console.error("Cannot clear state google",e)}}getGoogleState(){try{const e=window.localStorage.getItem(this.GOOGLE_STATE_KEY);if(!e)return null;const{accessToken:o,idToken:t}=JSON.parse(e);return{accessToken:o,idToken:t}}catch(e){return console.error("Cannot get state google",e),null}}async traditionalOAuth({scopes:e,hostedDomain:o,nonce:t,prompt:r}){var n;const i=[...new Set([...e||[],"openid"])],a=new URLSearchParams(Object.assign(Object.assign({client_id:(n=this.clientId)!==null&&n!==void 0?n:"",redirect_uri:this.redirectUrl||window.location.origin+window.location.pathname,response_type:this.loginType==="offline"?"code":"token id_token",scope:i.join(" ")},t&&{nonce:t}),{include_granted_scopes:"true",state:"popup"}));o!==void 0&&a.append("hd",o),r!==void 0&&a.append("prompt",r);const s=`https://accounts.google.com/o/oauth2/v2/auth?${a.toString()}`,l=500,c=600,h=window.screenX+(window.outerWidth-l)/2,u=window.screenY+(window.outerHeight-c)/2;localStorage.setItem(T.OAUTH_STATE_KEY,JSON.stringify({provider:"google",loginType:this.loginType,nonce:t}));const p=window.open(s,"Google Sign In",`width=${l},height=${c},left=${h},top=${u},popup=1`);let y,d;const O=`google_oauth_${t||Date.now()}`;let v=null;try{v=new BroadcastChannel(O)}catch{}return new Promise((I,S)=>{if(!p){S(new Error("Failed to open popup"));return}const U=()=>{window.removeEventListener("message",R),clearInterval(y),clearTimeout(d),v&&v.close()},P=_=>{if(this.loginType==="online"){const{accessToken:E,idToken:w}=_;if(E&&w){const m=this.parseJwt(w);this.persistStateGoogle(E.token,w),I({provider:"google",result:{accessToken:{token:E.token},idToken:w,profile:{email:m.email||null,familyName:m.family_name||null,givenName:m.given_name||null,id:m.sub||null,name:m.name||null,imageUrl:m.picture||null},responseType:"online"}})}else S(new Error("Invalid OAuth response: missing accessToken or idToken"))}else{const{serverAuthCode:E}=_;if(!E){S(new Error("Invalid OAuth response: missing serverAuthCode"));return}I({provider:"google",result:{responseType:"offline",serverAuthCode:E}})}},R=_=>{var E,w,m,b;if(!(_.origin!==window.location.origin||!((w=(E=_.data)===null||E===void 0?void 0:E.source)===null||w===void 0)&&w.startsWith("angular"))){if(((m=_.data)===null||m===void 0?void 0:m.type)==="oauth-response")U(),P(_.data);else if(((b=_.data)===null||b===void 0?void 0:b.type)==="oauth-error"){U();const A=_.data.error||"User cancelled the OAuth flow";S(new Error(A))}}};v&&(v.onmessage=_=>{var E;const w=_.data;if(!(!((E=w==null?void 0:w.source)===null||E===void 0)&&E.toString().startsWith("angular"))){if((w==null?void 0:w.type)==="oauth-response")U(),P(w);else if((w==null?void 0:w.type)==="oauth-error"){U();const m=w.error||"User cancelled the OAuth flow";S(new Error(m))}}}),window.addEventListener("message",R),d=setTimeout(()=>{U();try{p.close()}catch{}S(new Error("OAuth timeout"))},3e5),y=setInterval(()=>{try{p.closed&&(U(),S(new Error("Popup closed")))}catch{clearInterval(y)}},1e3)})}}var F=function(k,e){var o={};for(var t in k)Object.prototype.hasOwnProperty.call(k,t)&&e.indexOf(t)<0&&(o[t]=k[t]);if(k!=null&&typeof Object.getOwnPropertySymbols=="function")for(var r=0,t=Object.getOwnPropertySymbols(k);r<t.length;r++)e.indexOf(t[r])<0&&Object.prototype.propertyIsEnumerable.call(k,t[r])&&(o[t[r]]=k[t[r]]);return o};class J extends T{constructor(){super(...arguments),this.providers=new Map,this.TOKENS_KEY_PREFIX="capgo_social_login_oauth2_tokens_",this.STATE_PREFIX="capgo_social_login_oauth2_state_"}normalizeScopeValue(e){return e?typeof e=="string"?e:Array.isArray(e)?e.filter(Boolean).join(" "):"":""}normalizeConfig(e,o){var t,r,n,i,a,s,l,c;const h=(t=o.appId)!==null&&t!==void 0?t:o.clientId,u=(r=o.authorizationBaseUrl)!==null&&r!==void 0?r:o.authorizationEndpoint,p=(n=o.accessTokenEndpoint)!==null&&n!==void 0?n:o.tokenEndpoint,y=(i=o.logoutUrl)!==null&&i!==void 0?i:o.endSessionEndpoint,d=(a=o.scope)!==null&&a!==void 0?a:o.scopes;if(!h)throw new Error(`OAuth2 provider '${e}' requires appId (or clientId).`);if(!o.redirectUrl)throw new Error(`OAuth2 provider '${e}' requires redirectUrl.`);if(!u&&!o.issuerUrl)throw new Error(`OAuth2 provider '${e}' requires authorizationBaseUrl (or authorizationEndpoint) or issuerUrl.`);return{appId:h,issuerUrl:o.issuerUrl,authorizationBaseUrl:u,accessTokenEndpoint:p,redirectUrl:o.redirectUrl,resourceUrl:o.resourceUrl,responseType:(s=o.responseType)!==null&&s!==void 0?s:"code",pkceEnabled:(l=o.pkceEnabled)!==null&&l!==void 0?l:!0,scope:this.normalizeScopeValue(d),additionalParameters:o.additionalParameters,loginHint:o.loginHint,prompt:o.prompt,additionalTokenParameters:o.additionalTokenParameters,additionalResourceHeaders:o.additionalResourceHeaders,logoutUrl:y,postLogoutRedirectUrl:o.postLogoutRedirectUrl,additionalLogoutParameters:o.additionalLogoutParameters,logsEnabled:(c=o.logsEnabled)!==null&&c!==void 0?c:!1}}async ensureDiscovered(e){const o=this.providers.get(e);if(!(o!=null&&o.issuerUrl)||o.authorizationBaseUrl&&o.accessTokenEndpoint)return;const r=`${o.issuerUrl.replace(/\/+$/,"")}/.well-known/openid-configuration`,n=await fetch(r);if(!n.ok){const c=await n.text().catch(()=>"");throw new Error(`OAuth2 discovery failed (${n.status}): ${c||r}`)}const i=await n.json(),a=i.authorization_endpoint,s=i.token_endpoint,l=i.end_session_endpoint;!o.authorizationBaseUrl&&typeof a=="string"&&(o.authorizationBaseUrl=a),!o.accessTokenEndpoint&&typeof s=="string"&&(o.accessTokenEndpoint=s),!o.logoutUrl&&typeof l=="string"&&(o.logoutUrl=l),o.logsEnabled&&console.log(`[OAuth2:${e}] Discovery resolved`,{authorizationBaseUrl:o.authorizationBaseUrl,accessTokenEndpoint:o.accessTokenEndpoint,logoutUrl:o.logoutUrl})}async initializeProviders(e){for(const[o,t]of Object.entries(e)){const r=this.normalizeConfig(o,t);this.providers.set(o,r),r.logsEnabled&&console.log(`[OAuth2:${o}] Initialized with config:`,{appId:r.appId,issuerUrl:r.issuerUrl,authorizationBaseUrl:r.authorizationBaseUrl,redirectUrl:r.redirectUrl,responseType:r.responseType,pkceEnabled:r.pkceEnabled}),await this.ensureDiscovered(o)}}getProvider(e){const o=this.providers.get(e);if(!o)throw new Error(`OAuth2 provider '${e}' not configured. Call initialize() first.`);return o}getTokensKey(e){return`${this.TOKENS_KEY_PREFIX}${e}`}async login(e){var o,t,r,n,i,a,s,l,c;const{providerId:h}=e,u=this.getProvider(h);await this.ensureDiscovered(h);const p=(o=e.redirectUrl)!==null&&o!==void 0?o:u.redirectUrl,y=this.normalizeScopeValue((r=(t=e.scope)!==null&&t!==void 0?t:e.scopes)!==null&&r!==void 0?r:u.scope),d=(n=e.state)!==null&&n!==void 0?n:this.generateState(),O=(i=e.codeVerifier)!==null&&i!==void 0?i:this.generateCodeVerifier(),v=new URLSearchParams({response_type:u.responseType,client_id:u.appId,redirect_uri:p,state:d});y&&v.set("scope",y);const I=Object.assign(Object.assign({},(a=u.additionalParameters)!==null&&a!==void 0?a:{}),(s=e.additionalParameters)!==null&&s!==void 0?s:{}),S=(l=e.loginHint)!==null&&l!==void 0?l:u.loginHint,U=(c=e.prompt)!==null&&c!==void 0?c:u.prompt;if(S&&!("login_hint"in I)&&(I.login_hint=S),U&&!("prompt"in I)&&(I.prompt=U),u.responseType==="code"&&u.pkceEnabled){const b=await this.generateCodeChallenge(O);v.set("code_challenge",b),v.set("code_challenge_method","S256")}for(const[b,A]of Object.entries(I))A!==void 0&&v.set(b,A);if(this.persistPendingLogin(d,{providerId:h,codeVerifier:O,redirectUri:p,scope:y}),localStorage.setItem(T.OAUTH_STATE_KEY,JSON.stringify({provider:"oauth2",providerId:h,state:d})),!u.authorizationBaseUrl)throw new Error(`OAuth2 provider '${h}' is missing authorizationBaseUrl (discovery may have failed).`);const P=`${u.authorizationBaseUrl}?${v.toString()}`;if(u.logsEnabled&&console.log(`[OAuth2:${h}] Opening authorization URL:`,P),e.flow==="redirect")return window.location.assign(P),new Promise(()=>{});const R=500,_=650,E=window.screenX+(window.outerWidth-R)/2,w=window.screenY+(window.outerHeight-_)/2,m=window.open(P,"OAuth2Login",`width=${R},height=${_},left=${E},top=${w},popup=1`);return new Promise((b,A)=>{if(!m){A(new Error("Unable to open login window. Please allow popups."));return}const f=`oauth2_${d}`;let L=null;try{L=new BroadcastChannel(f)}catch{u.logsEnabled&&console.log(`[OAuth2:${h}] BroadcastChannel not supported, using postMessage only`)}const x=(g,D,G)=>{window.removeEventListener("message",g),clearTimeout(D),clearInterval(G),L&&L.close()},K=g=>{if((g==null?void 0:g.type)==="oauth-response"){if(g!=null&&g.provider&&g.provider!=="oauth2"||g!=null&&g.providerId&&g.providerId!==h)return!1;x($,N,z);const D=g,{provider:G,type:X}=D,H=F(D,["provider","type"]);return b({provider:"oauth2",result:H}),!0}else if((g==null?void 0:g.type)==="oauth-error")return g!=null&&g.provider&&g.provider!=="oauth2"?!1:(x($,N,z),A(new Error(g.error||"OAuth2 login was cancelled.")),!0);return!1};L&&(L.onmessage=g=>{K(g.data)});const $=g=>{g.origin===window.location.origin&&K(g.data)};window.addEventListener("message",$);const N=window.setTimeout(()=>{x($,N,z);try{m.close()}catch{}A(new Error("OAuth2 login timed out."))},3e5),z=window.setInterval(()=>{try{m.closed&&(x($,N,z),A(new Error("OAuth2 login window was closed.")))}catch{clearInterval(z),u.logsEnabled&&console.log(`[OAuth2:${h}] Cannot check popup.closed due to cross-origin restrictions. Relying on message handlers and timeout.`)}},1e3)})}async logout(e){await this.ensureDiscovered(e);const o=this.providers.get(e),t=this.getStoredTokens(e);if(localStorage.removeItem(this.getTokensKey(e)),o!=null&&o.logoutUrl)try{const r=new URL(o.logoutUrl);t!=null&&t.idToken&&r.searchParams.set("id_token_hint",t.idToken);const n=o.postLogoutRedirectUrl;if(n&&r.searchParams.set("post_logout_redirect_uri",n),o.additionalLogoutParameters)for(const[i,a]of Object.entries(o.additionalLogoutParameters))r.searchParams.set(i,a);window.open(r.toString(),"_blank")}catch{window.open(o.logoutUrl,"_blank")}}async isLoggedIn(e){const o=this.getStoredTokens(e);if(!o)return{isLoggedIn:!1};const t=o.expiresAt>Date.now();return t||localStorage.removeItem(this.getTokensKey(e)),{isLoggedIn:t}}async getAuthorizationCode(e){const o=this.getStoredTokens(e);if(!o)throw new Error(`OAuth2 access token is not available for provider '${e}'.`);return{accessToken:o.accessToken,jwt:o.idToken}}async refresh(e){await this.refreshToken(e)}async refreshToken(e,o,t){var r,n,i,a,s,l;await this.ensureDiscovered(e);const c=this.getProvider(e),h=this.getStoredTokens(e),u=o??(h==null?void 0:h.refreshToken);if(!u)throw new Error(`No OAuth2 refresh token is available for provider '${e}'. Include offline_access scope to receive one.`);if(!c.accessTokenEndpoint)throw new Error(`No accessTokenEndpoint configured for provider '${e}'.`);const p=await this.refreshWithRefreshToken(e,u,t),y=p.expires_in?Date.now()+p.expires_in*1e3:Date.now()+36e5,d=(i=(n=(r=p.scope)===null||r===void 0?void 0:r.split(" ").filter(Boolean))!==null&&n!==void 0?n:h==null?void 0:h.scope)!==null&&i!==void 0?i:[];let O=null;c.resourceUrl&&(O=await this.fetchResource(e,p.access_token));const v=(a=p.refresh_token)!==null&&a!==void 0?a:u;return this.persistTokens(e,{accessToken:p.access_token,refreshToken:v,idToken:p.id_token,expiresAt:y,scope:d,tokenType:p.token_type}),{providerId:e,accessToken:{token:p.access_token,tokenType:p.token_type,expires:new Date(y).toISOString(),refreshToken:v},idToken:(s=p.id_token)!==null&&s!==void 0?s:null,refreshToken:v??null,resourceData:O,scope:d,tokenType:p.token_type,expiresIn:(l=p.expires_in)!==null&&l!==void 0?l:null}}async handleOAuthRedirect(e,o){var t,r,n,i,a;const s=new URLSearchParams(e.search);new URLSearchParams(e.hash.slice(1)).forEach((d,O)=>{s.set(O,d)});const c=o??s.get("state");if(!c)return null;const h=this.consumePendingLogin(c);if(!h)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"OAuth2 login session expired or state mismatch."};const{providerId:u}=h;await this.ensureDiscovered(u);const p=this.providers.get(u);if(!p)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:`OAuth2 provider '${u}' configuration not found.`};const y=s.get("error");if(y)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:s.get("error_description")||y};try{let d;if(s.has("code")){const S=s.get("code");if(!S)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"OAuth2 authorization code missing from redirect."};d=await this.exchangeAuthorizationCode(u,S,h)}else if(s.has("access_token"))d={access_token:s.get("access_token"),token_type:s.get("token_type")||"bearer",expires_in:s.has("expires_in")?parseInt(s.get("expires_in"),10):void 0,scope:s.get("scope")||void 0,id_token:s.get("id_token")||void 0};else return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"No authorization code or access token in redirect."};const O=d.expires_in?Date.now()+d.expires_in*1e3:Date.now()+36e5,v=(r=(t=d.scope)===null||t===void 0?void 0:t.split(" ").filter(Boolean))!==null&&r!==void 0?r:[];let I=null;return p.resourceUrl&&(I=await this.fetchResource(u,d.access_token)),this.persistTokens(u,{accessToken:d.access_token,refreshToken:d.refresh_token,idToken:d.id_token,expiresAt:O,scope:v,tokenType:d.token_type}),{provider:"oauth2",result:{providerId:u,accessToken:{token:d.access_token,tokenType:d.token_type,expires:new Date(O).toISOString(),refreshToken:d.refresh_token},idToken:(n=d.id_token)!==null&&n!==void 0?n:null,refreshToken:(i=d.refresh_token)!==null&&i!==void 0?i:null,resourceData:I,scope:v,tokenType:d.token_type,expiresIn:(a=d.expires_in)!==null&&a!==void 0?a:null}}}catch(d){return d instanceof Error?{error:d.message}:{error:"OAuth2 login failed unexpectedly."}}finally{localStorage.removeItem(T.OAUTH_STATE_KEY)}}async exchangeAuthorizationCode(e,o,t){const r=this.getProvider(e);if(!r.accessTokenEndpoint)throw new Error(`No accessTokenEndpoint configured for provider '${e}'.`);const n=new URLSearchParams({grant_type:"authorization_code",client_id:r.appId,code:o,redirect_uri:t.redirectUri});if(r.pkceEnabled&&n.set("code_verifier",t.codeVerifier),r.additionalTokenParameters)for(const[a,s]of Object.entries(r.additionalTokenParameters))n.set(a,s);r.logsEnabled&&console.log(`[OAuth2:${e}] Exchanging code at:`,r.accessTokenEndpoint);const i=await fetch(r.accessTokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});if(!i.ok){const a=await i.text();throw new Error(`OAuth2 token exchange failed (${i.status}): ${a}`)}return await i.json()}async refreshWithRefreshToken(e,o,t){const r=this.getProvider(e);if(!r.accessTokenEndpoint)throw new Error(`No accessTokenEndpoint configured for provider '${e}'.`);const n=new URLSearchParams({grant_type:"refresh_token",refresh_token:o,client_id:r.appId});if(r.additionalTokenParameters)for(const[a,s]of Object.entries(r.additionalTokenParameters))n.set(a,s);if(t)for(const[a,s]of Object.entries(t))n.set(a,s);const i=await fetch(r.accessTokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});if(!i.ok){const a=await i.text();throw new Error(`OAuth2 refresh failed (${i.status}): ${a}`)}return await i.json()}async fetchResource(e,o){const t=this.getProvider(e);if(!t.resourceUrl)throw new Error(`No resourceUrl configured for provider '${e}'.`);const r={Authorization:`Bearer ${o}`};t.additionalResourceHeaders&&Object.assign(r,t.additionalResourceHeaders);const n=await fetch(t.resourceUrl,{headers:r});if(!n.ok){const i=await n.text();throw new Error(`Unable to fetch OAuth2 resource (${n.status}): ${i}`)}return await n.json()}persistTokens(e,o){localStorage.setItem(this.getTokensKey(e),JSON.stringify(o))}getStoredTokens(e){const o=localStorage.getItem(this.getTokensKey(e));if(!o)return null;try{return JSON.parse(o)}catch(t){return console.warn(`Failed to parse stored OAuth2 tokens for provider '${e}'`,t),null}}persistPendingLogin(e,o){localStorage.setItem(`${this.STATE_PREFIX}${e}`,JSON.stringify(o))}consumePendingLogin(e){const o=`${this.STATE_PREFIX}${e}`,t=localStorage.getItem(o);if(localStorage.removeItem(o),!t)return null;try{return JSON.parse(t)}catch(r){return console.warn("Failed to parse pending OAuth2 login payload",r),null}}generateState(){return[...crypto.getRandomValues(new Uint8Array(16))].map(e=>e.toString(16).padStart(2,"0")).join("")}generateCodeVerifier(){const e=new Uint8Array(64);return crypto.getRandomValues(e),Array.from(e).map(o=>"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"[o%66]).join("")}async generateCodeChallenge(e){const t=new TextEncoder().encode(e),r=await crypto.subtle.digest("SHA-256",t);return this.base64UrlEncode(new Uint8Array(r))}base64UrlEncode(e){let o="";return e.forEach(t=>o+=String.fromCharCode(t)),btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}decodeIdToken(e){const o=e.split(".");if(o.length<2)throw new Error("Invalid JWT: missing parts");const r=o[1].replace(/-/g,"+").replace(/_/g,"/"),n=r+"=".repeat((4-r.length%4)%4),i=atob(n);return JSON.parse(i)}getAccessTokenExpirationDate(e){const o=this.getStoredTokens(e);return o!=null&&o.expiresAt?{expirationDate:new Date(o.expiresAt).toISOString()}:{expirationDate:null}}isAccessTokenAvailable(e){const o=this.getStoredTokens(e);return{isAvailable:!!(o!=null&&o.accessToken)}}isAccessTokenExpired(e){const o=this.getStoredTokens(e);return o!=null&&o.expiresAt?{isExpired:o.expiresAt<=Date.now()}:{isExpired:!0}}isRefreshTokenAvailable(e){const o=this.getStoredTokens(e);return{isAvailable:!!(o!=null&&o.refreshToken)}}}var W=function(k,e){var o={};for(var t in k)Object.prototype.hasOwnProperty.call(k,t)&&e.indexOf(t)<0&&(o[t]=k[t]);if(k!=null&&typeof Object.getOwnPropertySymbols=="function")for(var r=0,t=Object.getOwnPropertySymbols(k);r<t.length;r++)e.indexOf(t[r])<0&&Object.prototype.propertyIsEnumerable.call(k,t[r])&&(o[t[r]]=k[t[r]]);return o};class M extends T{constructor(){super(...arguments),this.clientId=null,this.redirectUrl=null,this.defaultScopes=["tweet.read","users.read"],this.forceLogin=!1,this.TOKENS_KEY="capgo_social_login_twitter_tokens_v1",this.STATE_PREFIX="capgo_social_login_twitter_state_"}async initialize(e,o,t,r,n){this.clientId=e,this.redirectUrl=o??null,t!=null&&t.length&&(this.defaultScopes=t),this.forceLogin=r??!1,this.audience=n??void 0}async login(e){var o,t,r,n,i,a;if(!this.clientId)throw new Error("Twitter Client ID not configured. Call initialize() first.");const s=(t=(o=e.redirectUrl)!==null&&o!==void 0?o:this.redirectUrl)!==null&&t!==void 0?t:window.location.origin+window.location.pathname,l=!((r=e.scopes)===null||r===void 0)&&r.length?e.scopes:this.defaultScopes,c=(n=e.state)!==null&&n!==void 0?n:this.generateState(),h=(i=e.codeVerifier)!==null&&i!==void 0?i:this.generateCodeVerifier(),u=await this.generateCodeChallenge(h);this.persistPendingLogin(c,{codeVerifier:h,redirectUri:s,scopes:l}),localStorage.setItem(T.OAUTH_STATE_KEY,JSON.stringify({provider:"twitter",state:c}));const p=new URLSearchParams({response_type:"code",client_id:this.clientId,redirect_uri:s,scope:l.join(" "),state:c,code_challenge:u,code_challenge_method:"S256"});((a=e.forceLogin)!==null&&a!==void 0?a:this.forceLogin)===!0&&p.set("force_login","true"),this.audience&&p.set("audience",this.audience);const y=`https://x.com/i/oauth2/authorize?${p.toString()}`,d=500,O=650,v=window.screenX+(window.outerWidth-d)/2,I=window.screenY+(window.outerHeight-O)/2,S=window.open(y,"XLogin",`width=${d},height=${O},left=${v},top=${I},popup=1`);return new Promise((U,P)=>{if(!S){P(new Error("Unable to open login window. Please allow popups."));return}const R=`twitter_oauth_${c}`;let _=null;try{_=new BroadcastChannel(R)}catch{}const E=(f,L,x)=>{window.removeEventListener("message",f),clearTimeout(L),clearInterval(x),_&&_.close()},w=f=>{if((f==null?void 0:f.type)==="oauth-response"){if(f!=null&&f.provider&&f.provider!=="twitter")return!1;E(m,b,A);const L=f,{provider:x,type:K}=L,$=W(L,["provider","type"]);return U({provider:"twitter",result:$}),!0}else if((f==null?void 0:f.type)==="oauth-error")return f!=null&&f.provider&&f.provider!=="twitter"?!1:(E(m,b,A),P(new Error(f.error||"Twitter login was cancelled.")),!0);return!1};_&&(_.onmessage=f=>{w(f.data)});const m=f=>{f.origin===window.location.origin&&w(f.data)};window.addEventListener("message",m);const b=window.setTimeout(()=>{E(m,b,A);try{S.close()}catch{}P(new Error("Twitter login timed out."))},3e5),A=window.setInterval(()=>{try{S.closed&&(E(m,b,A),P(new Error("Twitter login window was closed.")))}catch{clearInterval(A)}},1e3)})}async logout(){localStorage.removeItem(this.TOKENS_KEY)}async isLoggedIn(){const e=this.getStoredTokens();if(!e)return{isLoggedIn:!1};const o=e.expiresAt>Date.now();return o||localStorage.removeItem(this.TOKENS_KEY),{isLoggedIn:o}}async getAuthorizationCode(){const e=this.getStoredTokens();if(!e)throw new Error("Twitter access token is not available.");return{accessToken:e.accessToken}}async refresh(){const e=this.getStoredTokens();if(!(e!=null&&e.refreshToken))throw new Error("No Twitter refresh token is available. Include offline.access scope to receive one.");await this.refreshWithRefreshToken(e.refreshToken)}async handleOAuthRedirect(e,o){const t=e.searchParams,r=o??t.get("state");if(!r)return null;const n=this.consumePendingLogin(r);if(!n)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"Twitter login session expired or state mismatch."};const i=t.get("error");if(i)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:t.get("error_description")||i};const a=t.get("code");if(!a)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"Twitter authorization code missing from redirect."};try{const s=await this.exchangeAuthorizationCode(a,n),l=await this.fetchProfile(s.access_token),c=Date.now()+s.expires_in*1e3,h=s.scope.split(" ").filter(Boolean);return this.persistTokens({accessToken:s.access_token,refreshToken:s.refresh_token,expiresAt:c,scope:h,tokenType:s.token_type,userId:l.id,profile:l}),{provider:"twitter",result:{accessToken:{token:s.access_token,tokenType:s.token_type,expires:new Date(c).toISOString(),userId:l.id},refreshToken:s.refresh_token,scope:h,tokenType:s.token_type,expiresIn:s.expires_in,profile:l}}}catch(s){return s instanceof Error?{error:s.message}:{error:"Twitter login failed unexpectedly."}}finally{localStorage.removeItem(T.OAUTH_STATE_KEY)}}async exchangeAuthorizationCode(e,o){var t;const r=new URLSearchParams({grant_type:"authorization_code",client_id:(t=this.clientId)!==null&&t!==void 0?t:"",code:e,redirect_uri:o.redirectUri,code_verifier:o.codeVerifier}),n=await fetch("https://api.x.com/2/oauth2/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!n.ok){const i=await n.text();throw new Error(`Twitter token exchange failed (${n.status}): ${i}`)}return await n.json()}async refreshWithRefreshToken(e){var o,t;const r=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:(o=this.clientId)!==null&&o!==void 0?o:""}),n=await fetch("https://api.x.com/2/oauth2/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!n.ok){const c=await n.text();throw new Error(`Twitter refresh failed (${n.status}): ${c}`)}const i=await n.json(),a=await this.fetchProfile(i.access_token),s=Date.now()+i.expires_in*1e3,l=i.scope.split(" ").filter(Boolean);this.persistTokens({accessToken:i.access_token,refreshToken:(t=i.refresh_token)!==null&&t!==void 0?t:e,expiresAt:s,scope:l,tokenType:i.token_type,userId:a.id,profile:a})}async fetchProfile(e){var o,t,r,n;const a=await fetch(`https://api.x.com/2/users/me?user.fields=${["profile_image_url","verified","name","username"].join(",")}`,{headers:{Authorization:`Bearer ${e}`}});if(!a.ok){const l=await a.text();throw new Error(`Unable to fetch Twitter profile (${a.status}): ${l}`)}const s=await a.json();if(!s.data)throw new Error("Twitter profile payload is missing data.");return{id:s.data.id,username:s.data.username,name:(o=s.data.name)!==null&&o!==void 0?o:null,profileImageUrl:(t=s.data.profile_image_url)!==null&&t!==void 0?t:null,verified:(r=s.data.verified)!==null&&r!==void 0?r:!1,email:(n=s.data.email)!==null&&n!==void 0?n:null}}persistTokens(e){localStorage.setItem(this.TOKENS_KEY,JSON.stringify(e))}getStoredTokens(){const e=localStorage.getItem(this.TOKENS_KEY);if(!e)return null;try{return JSON.parse(e)}catch(o){return console.warn("Failed to parse stored Twitter tokens",o),null}}persistPendingLogin(e,o){localStorage.setItem(`${this.STATE_PREFIX}${e}`,JSON.stringify(o))}consumePendingLogin(e){const o=`${this.STATE_PREFIX}${e}`,t=localStorage.getItem(o);if(localStorage.removeItem(o),!t)return null;try{return JSON.parse(t)}catch(r){return console.warn("Failed to parse pending Twitter login payload",r),null}}generateState(){return[...crypto.getRandomValues(new Uint8Array(16))].map(e=>e.toString(16).padStart(2,"0")).join("")}generateCodeVerifier(){const e=new Uint8Array(64);return crypto.getRandomValues(e),Array.from(e).map(o=>"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"[o%66]).join("")}async generateCodeChallenge(e){const t=new TextEncoder().encode(e),r=await crypto.subtle.digest("SHA-256",t);return this.base64UrlEncode(new Uint8Array(r))}base64UrlEncode(e){let o="";return e.forEach(t=>o+=String.fromCharCode(t)),btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}}class C extends j{constructor(){super(),this.googleProvider=new V,this.appleProvider=new B,this.facebookProvider=new Y,this.twitterProvider=new M,this.oauth2Provider=new J;const e=!!localStorage.getItem(C.OAUTH_STATE_KEY),o=!!window.opener||C.POPUP_WINDOW_NAMES.has(window.name);e&&o&&this.finishOAuthRedirectInPopup().catch(t=>{console.error("Failed to finish OAuth redirect",t);try{window.close()}catch{}})}async parseRedirectResult(){var e;const o=new URL(window.location.href),t=localStorage.getItem(C.OAUTH_STATE_KEY);let r=null,n,i;if(t)try{const s=JSON.parse(t);r=(e=s.provider)!==null&&e!==void 0?e:null,n=s.state,i=s.nonce}catch{r=t==="true"?"google":null}let a=null;switch(r){case"twitter":a=await this.twitterProvider.handleOAuthRedirect(o,n);break;case"oauth2":a=await this.oauth2Provider.handleOAuthRedirect(o,n);break;case"google":default:a=this.googleProvider.handleOAuthRedirect(o);break}return{provider:r,state:n,nonce:i,result:a}}async finishOAuthRedirectInPopup(){var e;const o=await this.parseRedirectResult(),t=o.result;if(!t)return;let r;"error"in t?r={type:"oauth-error",provider:(e=o.provider)!==null&&e!==void 0?e:null,error:t.error}:r=Object.assign({type:"oauth-response",provider:t.provider},t.result);try{window.opener&&window.opener.postMessage(r,window.location.origin)}catch{console.log("postMessage to opener failed, using BroadcastChannel")}try{let n=null;if(o.provider==="oauth2"&&o.state?n=`oauth2_${o.state}`:o.provider==="twitter"&&o.state?n=`twitter_oauth_${o.state}`:o.provider==="google"&&o.nonce&&(n=`google_oauth_${o.nonce}`),n){const i=new BroadcastChannel(n);i.postMessage(r),i.close()}}catch{console.log("BroadcastChannel not available")}window.close()}async initialize(e){var o,t,r,n;const i=[];!((o=e.google)===null||o===void 0)&&o.webClientId&&i.push(this.googleProvider.initialize(e.google.webClientId,e.google.mode,e.google.hostedDomain,e.google.redirectUrl)),!((t=e.apple)===null||t===void 0)&&t.clientId&&i.push(this.appleProvider.initialize(e.apple.clientId,e.apple.redirectUrl,e.apple.useProperTokenExchange)),!((r=e.facebook)===null||r===void 0)&&r.appId&&i.push(this.facebookProvider.initialize(e.facebook.appId,e.facebook.locale)),!((n=e.twitter)===null||n===void 0)&&n.clientId&&i.push(this.twitterProvider.initialize(e.twitter.clientId,e.twitter.redirectUrl,e.twitter.defaultScopes,e.twitter.forceLogin,e.twitter.audience)),e.oauth2&&Object.keys(e.oauth2).length>0&&i.push(this.oauth2Provider.initializeProviders(e.oauth2)),await Promise.all(i)}async login(e){switch(e.provider){case"google":return this.googleProvider.login(e.options);case"apple":return this.appleProvider.login(e.options);case"facebook":return this.facebookProvider.login(e.options);case"twitter":return this.twitterProvider.login(e.options);case"oauth2":return this.oauth2Provider.login(e.options);default:throw new Error(`Login for ${e.provider} is not implemented on web`)}}async logout(e){switch(e.provider){case"google":return this.googleProvider.logout();case"apple":return this.appleProvider.logout();case"facebook":return this.facebookProvider.logout();case"twitter":return this.twitterProvider.logout();case"oauth2":if(!e.providerId)throw new Error("providerId is required for oauth2 logout");return this.oauth2Provider.logout(e.providerId);default:throw new Error(`Logout for ${e.provider} is not implemented`)}}async isLoggedIn(e){switch(e.provider){case"google":return this.googleProvider.isLoggedIn();case"apple":return this.appleProvider.isLoggedIn();case"facebook":return this.facebookProvider.isLoggedIn();case"twitter":return this.twitterProvider.isLoggedIn();case"oauth2":if(!e.providerId)throw new Error("providerId is required for oauth2 isLoggedIn");return this.oauth2Provider.isLoggedIn(e.providerId);default:throw new Error(`isLoggedIn for ${e.provider} is not implemented`)}}async getAuthorizationCode(e){switch(e.provider){case"google":return this.googleProvider.getAuthorizationCode();case"apple":return this.appleProvider.getAuthorizationCode();case"facebook":return this.facebookProvider.getAuthorizationCode();case"twitter":return this.twitterProvider.getAuthorizationCode();case"oauth2":if(!e.providerId)throw new Error("providerId is required for oauth2 getAuthorizationCode");return this.oauth2Provider.getAuthorizationCode(e.providerId);default:throw new Error(`getAuthorizationCode for ${e.provider} is not implemented`)}}async refresh(e){switch(e.provider){case"google":return this.googleProvider.refresh();case"apple":return this.appleProvider.refresh();case"facebook":return this.facebookProvider.refresh(e.options);case"twitter":return this.twitterProvider.refresh();case"oauth2":{const o=e.options;if(!(o!=null&&o.providerId))throw new Error("providerId is required for oauth2 refresh");return this.oauth2Provider.refresh(o.providerId)}default:throw new Error(`Refresh for ${e.provider} is not implemented`)}}async providerSpecificCall(e){throw new Error(`Provider specific call for ${e.call} is not implemented`)}async refreshToken(e){if(e.provider!=="oauth2")throw new Error("refreshToken is only implemented for oauth2 on web");return this.oauth2Provider.refreshToken(e.providerId,e.refreshToken,e.additionalParameters)}async handleRedirectCallback(){const o=(await this.parseRedirectResult()).result;if(!o)return null;if("error"in o)throw new Error(o.error);return o}async decodeIdToken(e){var o;const t=(o=e==null?void 0:e.idToken)!==null&&o!==void 0?o:e==null?void 0:e.token;if(!t)throw new Error("idToken (or token) is required");return{claims:this.oauth2Provider.decodeIdToken(t)}}async getAccessTokenExpirationDate(e){if(typeof(e==null?void 0:e.accessTokenExpirationDate)!="number")throw new Error("accessTokenExpirationDate is required");return{date:new Date(e.accessTokenExpirationDate).toISOString()}}async isAccessTokenAvailable(e){var o;const t=(o=e==null?void 0:e.accessToken)!==null&&o!==void 0?o:null;return{isAvailable:typeof t=="string"&&t.length>0}}async isAccessTokenExpired(e){if(typeof(e==null?void 0:e.accessTokenExpirationDate)!="number")throw new Error("accessTokenExpirationDate is required");return{isExpired:e.accessTokenExpirationDate<=Date.now()}}async isRefreshTokenAvailable(e){var o;const t=(o=e==null?void 0:e.refreshToken)!==null&&o!==void 0?o:null;return{isAvailable:typeof t=="string"&&t.length>0}}async getPluginVersion(){return{version:"web"}}async openSecureWindow(e){const r=[["width",600],["height",550],["left",screen.width/2-300],["top",screen.height/2-275]].map(i=>i.join("=")).join(","),n=window.open(e.authEndpoint,"Authorization",r);return typeof n.focus=="function"&&n.focus(),new Promise((i,a)=>{const s=new BroadcastChannel(e.broadcastChannelName||"oauth-channel");s.addEventListener("message",l=>{l.data.startsWith(e.redirectUri)?(s.close(),i({redirectedUri:l.data})):(s.close(),a(new Error("Redirect URI does not match, expected "+e.redirectUri+" but got "+l.data)))}),setTimeout(()=>{s.close(),a(new Error("The sign-in flow timed out"))},5*6e4)})}}C.OAUTH_STATE_KEY="social_login_oauth_pending";C.POPUP_WINDOW_NAMES=new Set(["OAuth2Login","XLogin","Google Sign In","Authorization"]);export{C as SocialLoginWeb};
|
|
1
|
+
import{W as j}from"./index-STIPTMK8.js";class T extends j{constructor(){super()}parseJwt(e){const t=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),r=decodeURIComponent(atob(t).split("").map(n=>"%"+("00"+n.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(r)}async loadScript(e){return new Promise((o,t)=>{const r=document.createElement("script");r.src=e,r.async=!0,r.onload=()=>{o()},r.onerror=t,document.body.appendChild(r)})}}T.OAUTH_STATE_KEY="social_login_oauth_pending";class B extends T{constructor(){super(...arguments),this.clientId=null,this.redirectUrl=null,this.scriptLoaded=!1,this.scriptUrl="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js",this.useProperTokenExchange=!1}async initialize(e,o,t=!1){this.clientId=e,this.redirectUrl=o||null,this.useProperTokenExchange=t,e&&await this.loadAppleScript()}async login(e){if(!this.clientId)throw new Error("Apple Client ID not set. Call initialize() first.");if(!this.scriptLoaded)throw new Error("Apple Sign-In script not loaded.");return new Promise((o,t)=>{var r,n;AppleID.auth.init({clientId:(r=this.clientId)!==null&&r!==void 0?r:"",scope:((n=e.scopes)===null||n===void 0?void 0:n.join(" "))||"name email",redirectURI:this.redirectUrl||window.location.href,state:e.state,nonce:e.nonce,usePopup:!0}),AppleID.auth.signIn().then(i=>{var a,s,l,c,h;let u=null;this.useProperTokenExchange?u=null:u={token:i.authorization.code||""};const p=Object.assign({profile:{user:i.user||"",email:((a=i.user)===null||a===void 0?void 0:a.email)||null,givenName:((l=(s=i.user)===null||s===void 0?void 0:s.name)===null||l===void 0?void 0:l.firstName)||null,familyName:((h=(c=i.user)===null||c===void 0?void 0:c.name)===null||h===void 0?void 0:h.lastName)||null},accessToken:u,idToken:i.authorization.id_token||null},this.useProperTokenExchange&&{authorizationCode:i.authorization.code});o({provider:"apple",result:p})}).catch(i=>{t(i)})})}async logout(){console.log("Apple logout: Session should be managed on the client side")}async isLoggedIn(){return console.log("Apple login status should be managed on the client side"),{isLoggedIn:!1}}async getAuthorizationCode(){throw console.log("Apple authorization code should be stored during login"),new Error("Apple authorization code not available")}async refresh(){console.log("Apple refresh not available on web")}async loadAppleScript(){if(!this.scriptLoaded)return this.loadScript(this.scriptUrl).then(()=>{this.scriptLoaded=!0})}}class Y extends T{constructor(){super(...arguments),this.appId=null,this.scriptLoaded=!1,this.locale="en_US"}async initialize(e,o){this.appId=e,o&&(this.locale=o),e&&(await this.loadFacebookScript(this.locale),FB.init({appId:this.appId,version:"v17.0",xfbml:!0,cookie:!0}))}async login(e){if(!this.appId)throw new Error("Facebook App ID not set. Call initialize() first.");return new Promise((o,t)=>{FB.login(r=>{r.status==="connected"?FB.api("/me",{fields:"id,name,email,picture"},n=>{var i,a;const s={accessToken:{token:r.authResponse.accessToken,userId:r.authResponse.userID},profile:{userID:n.id,name:n.name,email:n.email||null,imageURL:((a=(i=n.picture)===null||i===void 0?void 0:i.data)===null||a===void 0?void 0:a.url)||null,friendIDs:[],birthday:null,ageRange:null,gender:null,location:null,hometown:null,profileURL:null},idToken:null};o({provider:"facebook",result:s})}):t(new Error("Facebook login failed"))},{scope:e.permissions.join(",")})})}async logout(){return new Promise(e=>{FB.logout(()=>e())})}async isLoggedIn(){return new Promise(e=>{FB.getLoginStatus(o=>{e({isLoggedIn:o.status==="connected"})})})}async getAuthorizationCode(){return new Promise((e,o)=>{FB.getLoginStatus(t=>{var r;t.status==="connected"?e({accessToken:((r=t.authResponse)===null||r===void 0?void 0:r.accessToken)||""}):o(new Error("No Facebook authorization code available"))})})}async refresh(e){await this.login(e)}async loadFacebookScript(e){if(this.scriptLoaded)return;const o=document.querySelector('script[src*="connect.facebook.net"]');return o&&o.remove(),this.loadScript(`https://connect.facebook.net/${e}/sdk.js`).then(()=>{this.scriptLoaded=!0})}}class V extends T{constructor(){super(...arguments),this.clientId=null,this.loginType="online",this.GOOGLE_TOKEN_REQUEST_URL="https://www.googleapis.com/oauth2/v3/tokeninfo",this.GOOGLE_STATE_KEY="capgo_social_login_google_state"}async initialize(e,o,t,r){this.clientId=e,o&&(this.loginType=o),this.hostedDomain=t,this.redirectUrl=r}async login(e){if(!this.clientId)throw new Error("Google Client ID not set. Call initialize() first.");let o=e.scopes||[];o.length>0?(o.includes("https://www.googleapis.com/auth/userinfo.email")||o.push("https://www.googleapis.com/auth/userinfo.email"),o.includes("https://www.googleapis.com/auth/userinfo.profile")||o.push("https://www.googleapis.com/auth/userinfo.profile"),o.includes("openid")||o.push("openid")):o=["https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/userinfo.profile","openid"];const t=e.nonce||Math.random().toString(36).substring(2);return this.traditionalOAuth({scopes:o,nonce:t,hostedDomain:this.hostedDomain,prompt:e.prompt})}async logout(){if(this.loginType==="offline")return Promise.reject("Offline login doesn't store tokens. logout is not available");const e=this.getGoogleState();e&&await this.rawLogoutGoogle(e.accessToken)}async isLoggedIn(){if(this.loginType==="offline")return Promise.reject("Offline login doesn't store tokens. isLoggedIn is not available");const e=this.getGoogleState();if(!e)return{isLoggedIn:!1};try{const o=await this.accessTokenIsValid(e.accessToken),t=this.idTokenValid(e.idToken);if(o&&t)return{isLoggedIn:!0};try{await this.rawLogoutGoogle(e.accessToken,!1)}catch(r){console.error("Access token is not valid, but cannot logout",r)}return{isLoggedIn:!1}}catch(o){return Promise.reject(o)}}async getAuthorizationCode(){if(this.loginType==="offline")return Promise.reject("Offline login doesn't store tokens. getAuthorizationCode is not available");const e=this.getGoogleState();if(!e)throw new Error("No Google authorization code available");try{const o=await this.accessTokenIsValid(e.accessToken),t=this.idTokenValid(e.idToken);if(o&&t)return{accessToken:e.accessToken,jwt:e.idToken};try{await this.rawLogoutGoogle(e.accessToken,!1)}catch(r){console.error("Access token is not valid, but cannot logout",r)}throw new Error("No Google authorization code available")}catch(o){return Promise.reject(o)}}async refresh(){return Promise.reject("Not implemented")}handleOAuthRedirect(e){const o=e.searchParams,t=o.get("error");if(t)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:o.get("error_description")||t};const r=o.get("code");if(r&&o.has("scope"))return{provider:"google",result:{serverAuthCode:r,responseType:"offline"}};const n=e.hash.substring(1);if(console.log("handleOAuthRedirect",e.hash),!n)return null;const i=new URLSearchParams(n),a=i.get("error");if(a)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:i.get("error_description")||a};console.log("handleOAuthRedirect ok");const s=i.get("access_token"),l=i.get("id_token");if(s&&l){localStorage.removeItem(T.OAUTH_STATE_KEY);const c=this.parseJwt(l);return{provider:"google",result:{accessToken:{token:s},idToken:l,profile:{email:c.email||null,familyName:c.family_name||null,givenName:c.given_name||null,id:c.sub||null,name:c.name||null,imageUrl:c.picture||null},responseType:"online"}}}return null}async accessTokenIsValid(e){const o=`${this.GOOGLE_TOKEN_REQUEST_URL}?access_token=${encodeURIComponent(e)}`;try{const t=await fetch(o);if(!t.ok)return console.log(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response not successful. Status code: ${t.status}. Assuming that the token is not valid`),!1;const r=await t.text();if(!r)throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is null`);let n;try{n=JSON.parse(r)}catch(s){throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${s}`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response body is not valid JSON. Error: ${s}`)}const i=n.expires_in;if(i==null)throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. Response JSON does not include 'expires_in'.`);let a;try{if(a=parseInt(i,10),isNaN(a))throw new Error("'expires_in' is not a valid integer")}catch(s){throw console.error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${i} is not a valid integer. Error: ${s}`),new Error(`Invalid response from ${this.GOOGLE_TOKEN_REQUEST_URL}. 'expires_in': ${i} is not a valid integer. Error: ${s}`)}return a>5}catch(t){throw console.error(t),t}}idTokenValid(e){try{const o=this.parseJwt(e),t=Math.ceil(Date.now()/1e3)+5;return o.exp&&t<o.exp}catch{return!1}}async rawLogoutGoogle(e,o=null){if(o===null&&(o=await this.accessTokenIsValid(e)),o===!0){try{await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${encodeURIComponent(e)}`),this.clearStateGoogle()}catch{}return}else{this.clearStateGoogle();return}}persistStateGoogle(e,o){try{window.localStorage.setItem(this.GOOGLE_STATE_KEY,JSON.stringify({accessToken:e,idToken:o}))}catch(t){console.error("Cannot persist state google",t)}}clearStateGoogle(){try{window.localStorage.removeItem(this.GOOGLE_STATE_KEY)}catch(e){console.error("Cannot clear state google",e)}}getGoogleState(){try{const e=window.localStorage.getItem(this.GOOGLE_STATE_KEY);if(!e)return null;const{accessToken:o,idToken:t}=JSON.parse(e);return{accessToken:o,idToken:t}}catch(e){return console.error("Cannot get state google",e),null}}async traditionalOAuth({scopes:e,hostedDomain:o,nonce:t,prompt:r}){var n;const i=[...new Set([...e||[],"openid"])],a=new URLSearchParams(Object.assign(Object.assign({client_id:(n=this.clientId)!==null&&n!==void 0?n:"",redirect_uri:this.redirectUrl||window.location.origin+window.location.pathname,response_type:this.loginType==="offline"?"code":"token id_token",scope:i.join(" ")},t&&{nonce:t}),{include_granted_scopes:"true",state:"popup"}));o!==void 0&&a.append("hd",o),r!==void 0&&a.append("prompt",r);const s=`https://accounts.google.com/o/oauth2/v2/auth?${a.toString()}`,l=500,c=600,h=window.screenX+(window.outerWidth-l)/2,u=window.screenY+(window.outerHeight-c)/2;localStorage.setItem(T.OAUTH_STATE_KEY,JSON.stringify({provider:"google",loginType:this.loginType,nonce:t}));const p=window.open(s,"Google Sign In",`width=${l},height=${c},left=${h},top=${u},popup=1`);let y,d;const O=`google_oauth_${t||Date.now()}`;let v=null;try{v=new BroadcastChannel(O)}catch{}return new Promise((I,S)=>{if(!p){S(new Error("Failed to open popup"));return}const U=()=>{window.removeEventListener("message",R),clearInterval(y),clearTimeout(d),v&&v.close()},P=_=>{if(this.loginType==="online"){const{accessToken:E,idToken:w}=_;if(E&&w){const m=this.parseJwt(w);this.persistStateGoogle(E.token,w),I({provider:"google",result:{accessToken:{token:E.token},idToken:w,profile:{email:m.email||null,familyName:m.family_name||null,givenName:m.given_name||null,id:m.sub||null,name:m.name||null,imageUrl:m.picture||null},responseType:"online"}})}else S(new Error("Invalid OAuth response: missing accessToken or idToken"))}else{const{serverAuthCode:E}=_;if(!E){S(new Error("Invalid OAuth response: missing serverAuthCode"));return}I({provider:"google",result:{responseType:"offline",serverAuthCode:E}})}},R=_=>{var E,w,m,b;if(!(_.origin!==window.location.origin||!((w=(E=_.data)===null||E===void 0?void 0:E.source)===null||w===void 0)&&w.startsWith("angular"))){if(((m=_.data)===null||m===void 0?void 0:m.type)==="oauth-response")U(),P(_.data);else if(((b=_.data)===null||b===void 0?void 0:b.type)==="oauth-error"){U();const A=_.data.error||"User cancelled the OAuth flow";S(new Error(A))}}};v&&(v.onmessage=_=>{var E;const w=_.data;if(!(!((E=w==null?void 0:w.source)===null||E===void 0)&&E.toString().startsWith("angular"))){if((w==null?void 0:w.type)==="oauth-response")U(),P(w);else if((w==null?void 0:w.type)==="oauth-error"){U();const m=w.error||"User cancelled the OAuth flow";S(new Error(m))}}}),window.addEventListener("message",R),d=setTimeout(()=>{U();try{p.close()}catch{}S(new Error("OAuth timeout"))},3e5),y=setInterval(()=>{try{p.closed&&(U(),S(new Error("Popup closed")))}catch{clearInterval(y)}},1e3)})}}var F=function(k,e){var o={};for(var t in k)Object.prototype.hasOwnProperty.call(k,t)&&e.indexOf(t)<0&&(o[t]=k[t]);if(k!=null&&typeof Object.getOwnPropertySymbols=="function")for(var r=0,t=Object.getOwnPropertySymbols(k);r<t.length;r++)e.indexOf(t[r])<0&&Object.prototype.propertyIsEnumerable.call(k,t[r])&&(o[t[r]]=k[t[r]]);return o};class J extends T{constructor(){super(...arguments),this.providers=new Map,this.TOKENS_KEY_PREFIX="capgo_social_login_oauth2_tokens_",this.STATE_PREFIX="capgo_social_login_oauth2_state_"}normalizeScopeValue(e){return e?typeof e=="string"?e:Array.isArray(e)?e.filter(Boolean).join(" "):"":""}normalizeConfig(e,o){var t,r,n,i,a,s,l,c;const h=(t=o.appId)!==null&&t!==void 0?t:o.clientId,u=(r=o.authorizationBaseUrl)!==null&&r!==void 0?r:o.authorizationEndpoint,p=(n=o.accessTokenEndpoint)!==null&&n!==void 0?n:o.tokenEndpoint,y=(i=o.logoutUrl)!==null&&i!==void 0?i:o.endSessionEndpoint,d=(a=o.scope)!==null&&a!==void 0?a:o.scopes;if(!h)throw new Error(`OAuth2 provider '${e}' requires appId (or clientId).`);if(!o.redirectUrl)throw new Error(`OAuth2 provider '${e}' requires redirectUrl.`);if(!u&&!o.issuerUrl)throw new Error(`OAuth2 provider '${e}' requires authorizationBaseUrl (or authorizationEndpoint) or issuerUrl.`);return{appId:h,issuerUrl:o.issuerUrl,authorizationBaseUrl:u,accessTokenEndpoint:p,redirectUrl:o.redirectUrl,resourceUrl:o.resourceUrl,responseType:(s=o.responseType)!==null&&s!==void 0?s:"code",pkceEnabled:(l=o.pkceEnabled)!==null&&l!==void 0?l:!0,scope:this.normalizeScopeValue(d),additionalParameters:o.additionalParameters,loginHint:o.loginHint,prompt:o.prompt,additionalTokenParameters:o.additionalTokenParameters,additionalResourceHeaders:o.additionalResourceHeaders,logoutUrl:y,postLogoutRedirectUrl:o.postLogoutRedirectUrl,additionalLogoutParameters:o.additionalLogoutParameters,logsEnabled:(c=o.logsEnabled)!==null&&c!==void 0?c:!1}}async ensureDiscovered(e){const o=this.providers.get(e);if(!(o!=null&&o.issuerUrl)||o.authorizationBaseUrl&&o.accessTokenEndpoint)return;const r=`${o.issuerUrl.replace(/\/+$/,"")}/.well-known/openid-configuration`,n=await fetch(r);if(!n.ok){const c=await n.text().catch(()=>"");throw new Error(`OAuth2 discovery failed (${n.status}): ${c||r}`)}const i=await n.json(),a=i.authorization_endpoint,s=i.token_endpoint,l=i.end_session_endpoint;!o.authorizationBaseUrl&&typeof a=="string"&&(o.authorizationBaseUrl=a),!o.accessTokenEndpoint&&typeof s=="string"&&(o.accessTokenEndpoint=s),!o.logoutUrl&&typeof l=="string"&&(o.logoutUrl=l),o.logsEnabled&&console.log(`[OAuth2:${e}] Discovery resolved`,{authorizationBaseUrl:o.authorizationBaseUrl,accessTokenEndpoint:o.accessTokenEndpoint,logoutUrl:o.logoutUrl})}async initializeProviders(e){for(const[o,t]of Object.entries(e)){const r=this.normalizeConfig(o,t);this.providers.set(o,r),r.logsEnabled&&console.log(`[OAuth2:${o}] Initialized with config:`,{appId:r.appId,issuerUrl:r.issuerUrl,authorizationBaseUrl:r.authorizationBaseUrl,redirectUrl:r.redirectUrl,responseType:r.responseType,pkceEnabled:r.pkceEnabled}),await this.ensureDiscovered(o)}}getProvider(e){const o=this.providers.get(e);if(!o)throw new Error(`OAuth2 provider '${e}' not configured. Call initialize() first.`);return o}getTokensKey(e){return`${this.TOKENS_KEY_PREFIX}${e}`}async login(e){var o,t,r,n,i,a,s,l,c;const{providerId:h}=e,u=this.getProvider(h);await this.ensureDiscovered(h);const p=(o=e.redirectUrl)!==null&&o!==void 0?o:u.redirectUrl,y=this.normalizeScopeValue((r=(t=e.scope)!==null&&t!==void 0?t:e.scopes)!==null&&r!==void 0?r:u.scope),d=(n=e.state)!==null&&n!==void 0?n:this.generateState(),O=(i=e.codeVerifier)!==null&&i!==void 0?i:this.generateCodeVerifier(),v=new URLSearchParams({response_type:u.responseType,client_id:u.appId,redirect_uri:p,state:d});y&&v.set("scope",y);const I=Object.assign(Object.assign({},(a=u.additionalParameters)!==null&&a!==void 0?a:{}),(s=e.additionalParameters)!==null&&s!==void 0?s:{}),S=(l=e.loginHint)!==null&&l!==void 0?l:u.loginHint,U=(c=e.prompt)!==null&&c!==void 0?c:u.prompt;if(S&&!("login_hint"in I)&&(I.login_hint=S),U&&!("prompt"in I)&&(I.prompt=U),u.responseType==="code"&&u.pkceEnabled){const b=await this.generateCodeChallenge(O);v.set("code_challenge",b),v.set("code_challenge_method","S256")}for(const[b,A]of Object.entries(I))A!==void 0&&v.set(b,A);if(this.persistPendingLogin(d,{providerId:h,codeVerifier:O,redirectUri:p,scope:y}),localStorage.setItem(T.OAUTH_STATE_KEY,JSON.stringify({provider:"oauth2",providerId:h,state:d})),!u.authorizationBaseUrl)throw new Error(`OAuth2 provider '${h}' is missing authorizationBaseUrl (discovery may have failed).`);const P=`${u.authorizationBaseUrl}?${v.toString()}`;if(u.logsEnabled&&console.log(`[OAuth2:${h}] Opening authorization URL:`,P),e.flow==="redirect")return window.location.assign(P),new Promise(()=>{});const R=500,_=650,E=window.screenX+(window.outerWidth-R)/2,w=window.screenY+(window.outerHeight-_)/2,m=window.open(P,"OAuth2Login",`width=${R},height=${_},left=${E},top=${w},popup=1`);return new Promise((b,A)=>{if(!m){A(new Error("Unable to open login window. Please allow popups."));return}const f=`oauth2_${d}`;let L=null;try{L=new BroadcastChannel(f)}catch{u.logsEnabled&&console.log(`[OAuth2:${h}] BroadcastChannel not supported, using postMessage only`)}const x=(g,D,G)=>{window.removeEventListener("message",g),clearTimeout(D),clearInterval(G),L&&L.close()},K=g=>{if((g==null?void 0:g.type)==="oauth-response"){if(g!=null&&g.provider&&g.provider!=="oauth2"||g!=null&&g.providerId&&g.providerId!==h)return!1;x($,N,z);const D=g,{provider:G,type:X}=D,H=F(D,["provider","type"]);return b({provider:"oauth2",result:H}),!0}else if((g==null?void 0:g.type)==="oauth-error")return g!=null&&g.provider&&g.provider!=="oauth2"?!1:(x($,N,z),A(new Error(g.error||"OAuth2 login was cancelled.")),!0);return!1};L&&(L.onmessage=g=>{K(g.data)});const $=g=>{g.origin===window.location.origin&&K(g.data)};window.addEventListener("message",$);const N=window.setTimeout(()=>{x($,N,z);try{m.close()}catch{}A(new Error("OAuth2 login timed out."))},3e5),z=window.setInterval(()=>{try{m.closed&&(x($,N,z),A(new Error("OAuth2 login window was closed.")))}catch{clearInterval(z),u.logsEnabled&&console.log(`[OAuth2:${h}] Cannot check popup.closed due to cross-origin restrictions. Relying on message handlers and timeout.`)}},1e3)})}async logout(e){await this.ensureDiscovered(e);const o=this.providers.get(e),t=this.getStoredTokens(e);if(localStorage.removeItem(this.getTokensKey(e)),o!=null&&o.logoutUrl)try{const r=new URL(o.logoutUrl);t!=null&&t.idToken&&r.searchParams.set("id_token_hint",t.idToken);const n=o.postLogoutRedirectUrl;if(n&&r.searchParams.set("post_logout_redirect_uri",n),o.additionalLogoutParameters)for(const[i,a]of Object.entries(o.additionalLogoutParameters))r.searchParams.set(i,a);window.open(r.toString(),"_blank")}catch{window.open(o.logoutUrl,"_blank")}}async isLoggedIn(e){const o=this.getStoredTokens(e);if(!o)return{isLoggedIn:!1};const t=o.expiresAt>Date.now();return t||localStorage.removeItem(this.getTokensKey(e)),{isLoggedIn:t}}async getAuthorizationCode(e){const o=this.getStoredTokens(e);if(!o)throw new Error(`OAuth2 access token is not available for provider '${e}'.`);return{accessToken:o.accessToken,jwt:o.idToken}}async refresh(e){await this.refreshToken(e)}async refreshToken(e,o,t){var r,n,i,a,s,l;await this.ensureDiscovered(e);const c=this.getProvider(e),h=this.getStoredTokens(e),u=o??(h==null?void 0:h.refreshToken);if(!u)throw new Error(`No OAuth2 refresh token is available for provider '${e}'. Include offline_access scope to receive one.`);if(!c.accessTokenEndpoint)throw new Error(`No accessTokenEndpoint configured for provider '${e}'.`);const p=await this.refreshWithRefreshToken(e,u,t),y=p.expires_in?Date.now()+p.expires_in*1e3:Date.now()+36e5,d=(i=(n=(r=p.scope)===null||r===void 0?void 0:r.split(" ").filter(Boolean))!==null&&n!==void 0?n:h==null?void 0:h.scope)!==null&&i!==void 0?i:[];let O=null;c.resourceUrl&&(O=await this.fetchResource(e,p.access_token));const v=(a=p.refresh_token)!==null&&a!==void 0?a:u;return this.persistTokens(e,{accessToken:p.access_token,refreshToken:v,idToken:p.id_token,expiresAt:y,scope:d,tokenType:p.token_type}),{providerId:e,accessToken:{token:p.access_token,tokenType:p.token_type,expires:new Date(y).toISOString(),refreshToken:v},idToken:(s=p.id_token)!==null&&s!==void 0?s:null,refreshToken:v??null,resourceData:O,scope:d,tokenType:p.token_type,expiresIn:(l=p.expires_in)!==null&&l!==void 0?l:null}}async handleOAuthRedirect(e,o){var t,r,n,i,a;const s=new URLSearchParams(e.search);new URLSearchParams(e.hash.slice(1)).forEach((d,O)=>{s.set(O,d)});const c=o??s.get("state");if(!c)return null;const h=this.consumePendingLogin(c);if(!h)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"OAuth2 login session expired or state mismatch."};const{providerId:u}=h;await this.ensureDiscovered(u);const p=this.providers.get(u);if(!p)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:`OAuth2 provider '${u}' configuration not found.`};const y=s.get("error");if(y)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:s.get("error_description")||y};try{let d;if(s.has("code")){const S=s.get("code");if(!S)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"OAuth2 authorization code missing from redirect."};d=await this.exchangeAuthorizationCode(u,S,h)}else if(s.has("access_token"))d={access_token:s.get("access_token"),token_type:s.get("token_type")||"bearer",expires_in:s.has("expires_in")?parseInt(s.get("expires_in"),10):void 0,scope:s.get("scope")||void 0,id_token:s.get("id_token")||void 0};else return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"No authorization code or access token in redirect."};const O=d.expires_in?Date.now()+d.expires_in*1e3:Date.now()+36e5,v=(r=(t=d.scope)===null||t===void 0?void 0:t.split(" ").filter(Boolean))!==null&&r!==void 0?r:[];let I=null;return p.resourceUrl&&(I=await this.fetchResource(u,d.access_token)),this.persistTokens(u,{accessToken:d.access_token,refreshToken:d.refresh_token,idToken:d.id_token,expiresAt:O,scope:v,tokenType:d.token_type}),{provider:"oauth2",result:{providerId:u,accessToken:{token:d.access_token,tokenType:d.token_type,expires:new Date(O).toISOString(),refreshToken:d.refresh_token},idToken:(n=d.id_token)!==null&&n!==void 0?n:null,refreshToken:(i=d.refresh_token)!==null&&i!==void 0?i:null,resourceData:I,scope:v,tokenType:d.token_type,expiresIn:(a=d.expires_in)!==null&&a!==void 0?a:null}}}catch(d){return d instanceof Error?{error:d.message}:{error:"OAuth2 login failed unexpectedly."}}finally{localStorage.removeItem(T.OAUTH_STATE_KEY)}}async exchangeAuthorizationCode(e,o,t){const r=this.getProvider(e);if(!r.accessTokenEndpoint)throw new Error(`No accessTokenEndpoint configured for provider '${e}'.`);const n=new URLSearchParams({grant_type:"authorization_code",client_id:r.appId,code:o,redirect_uri:t.redirectUri});if(r.pkceEnabled&&n.set("code_verifier",t.codeVerifier),r.additionalTokenParameters)for(const[a,s]of Object.entries(r.additionalTokenParameters))n.set(a,s);r.logsEnabled&&console.log(`[OAuth2:${e}] Exchanging code at:`,r.accessTokenEndpoint);const i=await fetch(r.accessTokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});if(!i.ok){const a=await i.text();throw new Error(`OAuth2 token exchange failed (${i.status}): ${a}`)}return await i.json()}async refreshWithRefreshToken(e,o,t){const r=this.getProvider(e);if(!r.accessTokenEndpoint)throw new Error(`No accessTokenEndpoint configured for provider '${e}'.`);const n=new URLSearchParams({grant_type:"refresh_token",refresh_token:o,client_id:r.appId});if(r.additionalTokenParameters)for(const[a,s]of Object.entries(r.additionalTokenParameters))n.set(a,s);if(t)for(const[a,s]of Object.entries(t))n.set(a,s);const i=await fetch(r.accessTokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});if(!i.ok){const a=await i.text();throw new Error(`OAuth2 refresh failed (${i.status}): ${a}`)}return await i.json()}async fetchResource(e,o){const t=this.getProvider(e);if(!t.resourceUrl)throw new Error(`No resourceUrl configured for provider '${e}'.`);const r={Authorization:`Bearer ${o}`};t.additionalResourceHeaders&&Object.assign(r,t.additionalResourceHeaders);const n=await fetch(t.resourceUrl,{headers:r});if(!n.ok){const i=await n.text();throw new Error(`Unable to fetch OAuth2 resource (${n.status}): ${i}`)}return await n.json()}persistTokens(e,o){localStorage.setItem(this.getTokensKey(e),JSON.stringify(o))}getStoredTokens(e){const o=localStorage.getItem(this.getTokensKey(e));if(!o)return null;try{return JSON.parse(o)}catch(t){return console.warn(`Failed to parse stored OAuth2 tokens for provider '${e}'`,t),null}}persistPendingLogin(e,o){localStorage.setItem(`${this.STATE_PREFIX}${e}`,JSON.stringify(o))}consumePendingLogin(e){const o=`${this.STATE_PREFIX}${e}`,t=localStorage.getItem(o);if(localStorage.removeItem(o),!t)return null;try{return JSON.parse(t)}catch(r){return console.warn("Failed to parse pending OAuth2 login payload",r),null}}generateState(){return[...crypto.getRandomValues(new Uint8Array(16))].map(e=>e.toString(16).padStart(2,"0")).join("")}generateCodeVerifier(){const e=new Uint8Array(64);return crypto.getRandomValues(e),Array.from(e).map(o=>"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"[o%66]).join("")}async generateCodeChallenge(e){const t=new TextEncoder().encode(e),r=await crypto.subtle.digest("SHA-256",t);return this.base64UrlEncode(new Uint8Array(r))}base64UrlEncode(e){let o="";return e.forEach(t=>o+=String.fromCharCode(t)),btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}decodeIdToken(e){const o=e.split(".");if(o.length<2)throw new Error("Invalid JWT: missing parts");const r=o[1].replace(/-/g,"+").replace(/_/g,"/"),n=r+"=".repeat((4-r.length%4)%4),i=atob(n);return JSON.parse(i)}getAccessTokenExpirationDate(e){const o=this.getStoredTokens(e);return o!=null&&o.expiresAt?{expirationDate:new Date(o.expiresAt).toISOString()}:{expirationDate:null}}isAccessTokenAvailable(e){const o=this.getStoredTokens(e);return{isAvailable:!!(o!=null&&o.accessToken)}}isAccessTokenExpired(e){const o=this.getStoredTokens(e);return o!=null&&o.expiresAt?{isExpired:o.expiresAt<=Date.now()}:{isExpired:!0}}isRefreshTokenAvailable(e){const o=this.getStoredTokens(e);return{isAvailable:!!(o!=null&&o.refreshToken)}}}var W=function(k,e){var o={};for(var t in k)Object.prototype.hasOwnProperty.call(k,t)&&e.indexOf(t)<0&&(o[t]=k[t]);if(k!=null&&typeof Object.getOwnPropertySymbols=="function")for(var r=0,t=Object.getOwnPropertySymbols(k);r<t.length;r++)e.indexOf(t[r])<0&&Object.prototype.propertyIsEnumerable.call(k,t[r])&&(o[t[r]]=k[t[r]]);return o};class M extends T{constructor(){super(...arguments),this.clientId=null,this.redirectUrl=null,this.defaultScopes=["tweet.read","users.read"],this.forceLogin=!1,this.TOKENS_KEY="capgo_social_login_twitter_tokens_v1",this.STATE_PREFIX="capgo_social_login_twitter_state_"}async initialize(e,o,t,r,n){this.clientId=e,this.redirectUrl=o??null,t!=null&&t.length&&(this.defaultScopes=t),this.forceLogin=r??!1,this.audience=n??void 0}async login(e){var o,t,r,n,i,a;if(!this.clientId)throw new Error("Twitter Client ID not configured. Call initialize() first.");const s=(t=(o=e.redirectUrl)!==null&&o!==void 0?o:this.redirectUrl)!==null&&t!==void 0?t:window.location.origin+window.location.pathname,l=!((r=e.scopes)===null||r===void 0)&&r.length?e.scopes:this.defaultScopes,c=(n=e.state)!==null&&n!==void 0?n:this.generateState(),h=(i=e.codeVerifier)!==null&&i!==void 0?i:this.generateCodeVerifier(),u=await this.generateCodeChallenge(h);this.persistPendingLogin(c,{codeVerifier:h,redirectUri:s,scopes:l}),localStorage.setItem(T.OAUTH_STATE_KEY,JSON.stringify({provider:"twitter",state:c}));const p=new URLSearchParams({response_type:"code",client_id:this.clientId,redirect_uri:s,scope:l.join(" "),state:c,code_challenge:u,code_challenge_method:"S256"});((a=e.forceLogin)!==null&&a!==void 0?a:this.forceLogin)===!0&&p.set("force_login","true"),this.audience&&p.set("audience",this.audience);const y=`https://x.com/i/oauth2/authorize?${p.toString()}`,d=500,O=650,v=window.screenX+(window.outerWidth-d)/2,I=window.screenY+(window.outerHeight-O)/2,S=window.open(y,"XLogin",`width=${d},height=${O},left=${v},top=${I},popup=1`);return new Promise((U,P)=>{if(!S){P(new Error("Unable to open login window. Please allow popups."));return}const R=`twitter_oauth_${c}`;let _=null;try{_=new BroadcastChannel(R)}catch{}const E=(f,L,x)=>{window.removeEventListener("message",f),clearTimeout(L),clearInterval(x),_&&_.close()},w=f=>{if((f==null?void 0:f.type)==="oauth-response"){if(f!=null&&f.provider&&f.provider!=="twitter")return!1;E(m,b,A);const L=f,{provider:x,type:K}=L,$=W(L,["provider","type"]);return U({provider:"twitter",result:$}),!0}else if((f==null?void 0:f.type)==="oauth-error")return f!=null&&f.provider&&f.provider!=="twitter"?!1:(E(m,b,A),P(new Error(f.error||"Twitter login was cancelled.")),!0);return!1};_&&(_.onmessage=f=>{w(f.data)});const m=f=>{f.origin===window.location.origin&&w(f.data)};window.addEventListener("message",m);const b=window.setTimeout(()=>{E(m,b,A);try{S.close()}catch{}P(new Error("Twitter login timed out."))},3e5),A=window.setInterval(()=>{try{S.closed&&(E(m,b,A),P(new Error("Twitter login window was closed.")))}catch{clearInterval(A)}},1e3)})}async logout(){localStorage.removeItem(this.TOKENS_KEY)}async isLoggedIn(){const e=this.getStoredTokens();if(!e)return{isLoggedIn:!1};const o=e.expiresAt>Date.now();return o||localStorage.removeItem(this.TOKENS_KEY),{isLoggedIn:o}}async getAuthorizationCode(){const e=this.getStoredTokens();if(!e)throw new Error("Twitter access token is not available.");return{accessToken:e.accessToken}}async refresh(){const e=this.getStoredTokens();if(!(e!=null&&e.refreshToken))throw new Error("No Twitter refresh token is available. Include offline.access scope to receive one.");await this.refreshWithRefreshToken(e.refreshToken)}async handleOAuthRedirect(e,o){const t=e.searchParams,r=o??t.get("state");if(!r)return null;const n=this.consumePendingLogin(r);if(!n)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"Twitter login session expired or state mismatch."};const i=t.get("error");if(i)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:t.get("error_description")||i};const a=t.get("code");if(!a)return localStorage.removeItem(T.OAUTH_STATE_KEY),{error:"Twitter authorization code missing from redirect."};try{const s=await this.exchangeAuthorizationCode(a,n),l=await this.fetchProfile(s.access_token),c=Date.now()+s.expires_in*1e3,h=s.scope.split(" ").filter(Boolean);return this.persistTokens({accessToken:s.access_token,refreshToken:s.refresh_token,expiresAt:c,scope:h,tokenType:s.token_type,userId:l.id,profile:l}),{provider:"twitter",result:{accessToken:{token:s.access_token,tokenType:s.token_type,expires:new Date(c).toISOString(),userId:l.id},refreshToken:s.refresh_token,scope:h,tokenType:s.token_type,expiresIn:s.expires_in,profile:l}}}catch(s){return s instanceof Error?{error:s.message}:{error:"Twitter login failed unexpectedly."}}finally{localStorage.removeItem(T.OAUTH_STATE_KEY)}}async exchangeAuthorizationCode(e,o){var t;const r=new URLSearchParams({grant_type:"authorization_code",client_id:(t=this.clientId)!==null&&t!==void 0?t:"",code:e,redirect_uri:o.redirectUri,code_verifier:o.codeVerifier}),n=await fetch("https://api.x.com/2/oauth2/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!n.ok){const i=await n.text();throw new Error(`Twitter token exchange failed (${n.status}): ${i}`)}return await n.json()}async refreshWithRefreshToken(e){var o,t;const r=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:(o=this.clientId)!==null&&o!==void 0?o:""}),n=await fetch("https://api.x.com/2/oauth2/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!n.ok){const c=await n.text();throw new Error(`Twitter refresh failed (${n.status}): ${c}`)}const i=await n.json(),a=await this.fetchProfile(i.access_token),s=Date.now()+i.expires_in*1e3,l=i.scope.split(" ").filter(Boolean);this.persistTokens({accessToken:i.access_token,refreshToken:(t=i.refresh_token)!==null&&t!==void 0?t:e,expiresAt:s,scope:l,tokenType:i.token_type,userId:a.id,profile:a})}async fetchProfile(e){var o,t,r,n;const a=await fetch(`https://api.x.com/2/users/me?user.fields=${["profile_image_url","verified","name","username"].join(",")}`,{headers:{Authorization:`Bearer ${e}`}});if(!a.ok){const l=await a.text();throw new Error(`Unable to fetch Twitter profile (${a.status}): ${l}`)}const s=await a.json();if(!s.data)throw new Error("Twitter profile payload is missing data.");return{id:s.data.id,username:s.data.username,name:(o=s.data.name)!==null&&o!==void 0?o:null,profileImageUrl:(t=s.data.profile_image_url)!==null&&t!==void 0?t:null,verified:(r=s.data.verified)!==null&&r!==void 0?r:!1,email:(n=s.data.email)!==null&&n!==void 0?n:null}}persistTokens(e){localStorage.setItem(this.TOKENS_KEY,JSON.stringify(e))}getStoredTokens(){const e=localStorage.getItem(this.TOKENS_KEY);if(!e)return null;try{return JSON.parse(e)}catch(o){return console.warn("Failed to parse stored Twitter tokens",o),null}}persistPendingLogin(e,o){localStorage.setItem(`${this.STATE_PREFIX}${e}`,JSON.stringify(o))}consumePendingLogin(e){const o=`${this.STATE_PREFIX}${e}`,t=localStorage.getItem(o);if(localStorage.removeItem(o),!t)return null;try{return JSON.parse(t)}catch(r){return console.warn("Failed to parse pending Twitter login payload",r),null}}generateState(){return[...crypto.getRandomValues(new Uint8Array(16))].map(e=>e.toString(16).padStart(2,"0")).join("")}generateCodeVerifier(){const e=new Uint8Array(64);return crypto.getRandomValues(e),Array.from(e).map(o=>"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"[o%66]).join("")}async generateCodeChallenge(e){const t=new TextEncoder().encode(e),r=await crypto.subtle.digest("SHA-256",t);return this.base64UrlEncode(new Uint8Array(r))}base64UrlEncode(e){let o="";return e.forEach(t=>o+=String.fromCharCode(t)),btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}}class C extends j{constructor(){super(),this.googleProvider=new V,this.appleProvider=new B,this.facebookProvider=new Y,this.twitterProvider=new M,this.oauth2Provider=new J;const e=!!localStorage.getItem(C.OAUTH_STATE_KEY),o=!!window.opener||C.POPUP_WINDOW_NAMES.has(window.name);e&&o&&this.finishOAuthRedirectInPopup().catch(t=>{console.error("Failed to finish OAuth redirect",t);try{window.close()}catch{}})}async parseRedirectResult(){var e;const o=new URL(window.location.href),t=localStorage.getItem(C.OAUTH_STATE_KEY);let r=null,n,i;if(t)try{const s=JSON.parse(t);r=(e=s.provider)!==null&&e!==void 0?e:null,n=s.state,i=s.nonce}catch{r=t==="true"?"google":null}let a=null;switch(r){case"twitter":a=await this.twitterProvider.handleOAuthRedirect(o,n);break;case"oauth2":a=await this.oauth2Provider.handleOAuthRedirect(o,n);break;case"google":default:a=this.googleProvider.handleOAuthRedirect(o);break}return{provider:r,state:n,nonce:i,result:a}}async finishOAuthRedirectInPopup(){var e;const o=await this.parseRedirectResult(),t=o.result;if(!t)return;let r;"error"in t?r={type:"oauth-error",provider:(e=o.provider)!==null&&e!==void 0?e:null,error:t.error}:r=Object.assign({type:"oauth-response",provider:t.provider},t.result);try{window.opener&&window.opener.postMessage(r,window.location.origin)}catch{console.log("postMessage to opener failed, using BroadcastChannel")}try{let n=null;if(o.provider==="oauth2"&&o.state?n=`oauth2_${o.state}`:o.provider==="twitter"&&o.state?n=`twitter_oauth_${o.state}`:o.provider==="google"&&o.nonce&&(n=`google_oauth_${o.nonce}`),n){const i=new BroadcastChannel(n);i.postMessage(r),i.close()}}catch{console.log("BroadcastChannel not available")}window.close()}async initialize(e){var o,t,r,n;const i=[];!((o=e.google)===null||o===void 0)&&o.webClientId&&i.push(this.googleProvider.initialize(e.google.webClientId,e.google.mode,e.google.hostedDomain,e.google.redirectUrl)),!((t=e.apple)===null||t===void 0)&&t.clientId&&i.push(this.appleProvider.initialize(e.apple.clientId,e.apple.redirectUrl,e.apple.useProperTokenExchange)),!((r=e.facebook)===null||r===void 0)&&r.appId&&i.push(this.facebookProvider.initialize(e.facebook.appId,e.facebook.locale)),!((n=e.twitter)===null||n===void 0)&&n.clientId&&i.push(this.twitterProvider.initialize(e.twitter.clientId,e.twitter.redirectUrl,e.twitter.defaultScopes,e.twitter.forceLogin,e.twitter.audience)),e.oauth2&&Object.keys(e.oauth2).length>0&&i.push(this.oauth2Provider.initializeProviders(e.oauth2)),await Promise.all(i)}async login(e){switch(e.provider){case"google":return this.googleProvider.login(e.options);case"apple":return this.appleProvider.login(e.options);case"facebook":return this.facebookProvider.login(e.options);case"twitter":return this.twitterProvider.login(e.options);case"oauth2":return this.oauth2Provider.login(e.options);default:throw new Error(`Login for ${e.provider} is not implemented on web`)}}async logout(e){switch(e.provider){case"google":return this.googleProvider.logout();case"apple":return this.appleProvider.logout();case"facebook":return this.facebookProvider.logout();case"twitter":return this.twitterProvider.logout();case"oauth2":if(!e.providerId)throw new Error("providerId is required for oauth2 logout");return this.oauth2Provider.logout(e.providerId);default:throw new Error(`Logout for ${e.provider} is not implemented`)}}async isLoggedIn(e){switch(e.provider){case"google":return this.googleProvider.isLoggedIn();case"apple":return this.appleProvider.isLoggedIn();case"facebook":return this.facebookProvider.isLoggedIn();case"twitter":return this.twitterProvider.isLoggedIn();case"oauth2":if(!e.providerId)throw new Error("providerId is required for oauth2 isLoggedIn");return this.oauth2Provider.isLoggedIn(e.providerId);default:throw new Error(`isLoggedIn for ${e.provider} is not implemented`)}}async getAuthorizationCode(e){switch(e.provider){case"google":return this.googleProvider.getAuthorizationCode();case"apple":return this.appleProvider.getAuthorizationCode();case"facebook":return this.facebookProvider.getAuthorizationCode();case"twitter":return this.twitterProvider.getAuthorizationCode();case"oauth2":if(!e.providerId)throw new Error("providerId is required for oauth2 getAuthorizationCode");return this.oauth2Provider.getAuthorizationCode(e.providerId);default:throw new Error(`getAuthorizationCode for ${e.provider} is not implemented`)}}async refresh(e){switch(e.provider){case"google":return this.googleProvider.refresh();case"apple":return this.appleProvider.refresh();case"facebook":return this.facebookProvider.refresh(e.options);case"twitter":return this.twitterProvider.refresh();case"oauth2":{const o=e.options;if(!(o!=null&&o.providerId))throw new Error("providerId is required for oauth2 refresh");return this.oauth2Provider.refresh(o.providerId)}default:throw new Error(`Refresh for ${e.provider} is not implemented`)}}async providerSpecificCall(e){throw new Error(`Provider specific call for ${e.call} is not implemented`)}async refreshToken(e){if(e.provider!=="oauth2")throw new Error("refreshToken is only implemented for oauth2 on web");return this.oauth2Provider.refreshToken(e.providerId,e.refreshToken,e.additionalParameters)}async handleRedirectCallback(){const o=(await this.parseRedirectResult()).result;if(!o)return null;if("error"in o)throw new Error(o.error);return o}async decodeIdToken(e){var o;const t=(o=e==null?void 0:e.idToken)!==null&&o!==void 0?o:e==null?void 0:e.token;if(!t)throw new Error("idToken (or token) is required");return{claims:this.oauth2Provider.decodeIdToken(t)}}async getAccessTokenExpirationDate(e){if(typeof(e==null?void 0:e.accessTokenExpirationDate)!="number")throw new Error("accessTokenExpirationDate is required");return{date:new Date(e.accessTokenExpirationDate).toISOString()}}async isAccessTokenAvailable(e){var o;const t=(o=e==null?void 0:e.accessToken)!==null&&o!==void 0?o:null;return{isAvailable:typeof t=="string"&&t.length>0}}async isAccessTokenExpired(e){if(typeof(e==null?void 0:e.accessTokenExpirationDate)!="number")throw new Error("accessTokenExpirationDate is required");return{isExpired:e.accessTokenExpirationDate<=Date.now()}}async isRefreshTokenAvailable(e){var o;const t=(o=e==null?void 0:e.refreshToken)!==null&&o!==void 0?o:null;return{isAvailable:typeof t=="string"&&t.length>0}}async getPluginVersion(){return{version:"web"}}async openSecureWindow(e){const r=[["width",600],["height",550],["left",screen.width/2-300],["top",screen.height/2-275]].map(i=>i.join("=")).join(","),n=window.open(e.authEndpoint,"Authorization",r);return typeof n.focus=="function"&&n.focus(),new Promise((i,a)=>{const s=new BroadcastChannel(e.broadcastChannelName||"oauth-channel");s.addEventListener("message",l=>{l.data.startsWith(e.redirectUri)?(s.close(),i({redirectedUri:l.data})):(s.close(),a(new Error("Redirect URI does not match, expected "+e.redirectUri+" but got "+l.data)))}),setTimeout(()=>{s.close(),a(new Error("The sign-in flow timed out"))},5*6e4)})}}C.OAUTH_STATE_KEY="social_login_oauth_pending";C.POPUP_WINDOW_NAMES=new Set(["OAuth2Login","XLogin","Google Sign In","Authorization"]);export{C as SocialLoginWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as t}from"./index-
|
|
1
|
+
import{W as t}from"./index-STIPTMK8.js";class s extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}}export{s as AppWeb};
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet" />
|
|
31
31
|
|
|
32
32
|
<title>BotsChat</title>
|
|
33
|
-
<script type="module" crossorigin src="/assets/index-
|
|
34
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
33
|
+
<script type="module" crossorigin src="/assets/index-STIPTMK8.js"></script>
|
|
34
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BtPyCBCl.css">
|
|
35
35
|
</head>
|
|
36
36
|
<body>
|
|
37
37
|
<div id="root"></div>
|
package/packages/web/src/App.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type ActiveView,
|
|
12
12
|
type ActivityItem,
|
|
13
13
|
} from "./store";
|
|
14
|
-
import { getToken, setToken, setRefreshToken, agentsApi, channelsApi, tasksApi, jobsApi, authApi, messagesApi, modelsApi, meApi, sessionsApi, type ModelInfo
|
|
14
|
+
import { getToken, setToken, setRefreshToken, agentsApi, channelsApi, tasksApi, jobsApi, authApi, messagesApi, modelsApi, meApi, sessionsApi, type ModelInfo } from "./api";
|
|
15
15
|
import { ModelSelect } from "./components/ModelSelect";
|
|
16
16
|
import { BotsChatWSClient, type WSMessage } from "./ws";
|
|
17
17
|
import { initPushNotifications, getPendingPushNav, clearPendingPushNav, notifyIfBackground } from "./push";
|
|
@@ -27,13 +27,13 @@ import { OnboardingPage } from "./components/OnboardingPage";
|
|
|
27
27
|
import { ConnectionSettings } from "./components/ConnectionSettings";
|
|
28
28
|
import { E2ESettings } from "./components/E2ESettings";
|
|
29
29
|
import { AccountSettings } from "./components/AccountSettings";
|
|
30
|
-
import { AgentSettings } from "./components/AgentSettings";
|
|
31
30
|
import { DebugLogPanel } from "./components/DebugLogPanel";
|
|
32
31
|
import { CronSidebar } from "./components/CronSidebar";
|
|
33
32
|
import { CronDetail } from "./components/CronDetail";
|
|
34
33
|
import { ResizeHandle } from "./components/ResizeHandle";
|
|
35
34
|
import { useIsMobile } from "./hooks/useIsMobile";
|
|
36
35
|
import { MobileLayout } from "./components/MobileLayout";
|
|
36
|
+
import { ImageLightbox } from "./components/ImageLightbox";
|
|
37
37
|
import { dlog } from "./debug-log";
|
|
38
38
|
import { E2eService } from "./e2e";
|
|
39
39
|
import { gtagPageView } from "./analytics";
|
|
@@ -56,8 +56,7 @@ export default function App() {
|
|
|
56
56
|
const pushNavTargetRef = useRef<string | null>(null);
|
|
57
57
|
|
|
58
58
|
const [showSettings, setShowSettings] = useState(false);
|
|
59
|
-
const [settingsTab, setSettingsTab] = useState<"general" | "
|
|
60
|
-
const [showAgentSettings, setShowAgentSettings] = useState(false);
|
|
59
|
+
const [settingsTab, setSettingsTab] = useState<"general" | "connection" | "security">("general");
|
|
61
60
|
|
|
62
61
|
// Track whether the initial channels fetch has completed (prevents onboarding flash)
|
|
63
62
|
const [channelsLoadedOnce, setChannelsLoadedOnce] = useState(false);
|
|
@@ -436,7 +435,7 @@ export default function App() {
|
|
|
436
435
|
dispatch({ type: "SET_SESSIONS", sessions: [] });
|
|
437
436
|
|
|
438
437
|
channelsApi
|
|
439
|
-
.create({ name: "General",
|
|
438
|
+
.create({ name: "General", openclawAgentId: "main" })
|
|
440
439
|
.then(async (channel) => {
|
|
441
440
|
dlog.info("Sessions", `General channel created: ${channel.id}`);
|
|
442
441
|
// Reload agents and channels so the default agent picks up the new channelId
|
|
@@ -555,11 +554,11 @@ export default function App() {
|
|
|
555
554
|
|
|
556
555
|
const match = baseKey.match(/^agent:([^:]+):/);
|
|
557
556
|
if (!match) return;
|
|
558
|
-
const
|
|
557
|
+
const openclawAgentId = match[1];
|
|
559
558
|
|
|
560
559
|
const st = stateRef.current;
|
|
561
560
|
const targetAgent = st.agents.find((a) =>
|
|
562
|
-
a.sessionKey.startsWith(`agent:${
|
|
561
|
+
a.sessionKey.startsWith(`agent:${openclawAgentId}:`),
|
|
563
562
|
);
|
|
564
563
|
if (!targetAgent) return;
|
|
565
564
|
|
|
@@ -643,12 +642,6 @@ export default function App() {
|
|
|
643
642
|
if (Array.isArray(msg.models) && msg.models.length > 0) {
|
|
644
643
|
dispatch({ type: "SET_MODELS", models: msg.models as ModelInfo[] });
|
|
645
644
|
}
|
|
646
|
-
const data = msg as unknown as { connectedAgents?: { id: string }[] };
|
|
647
|
-
if (Array.isArray(data.connectedAgents)) {
|
|
648
|
-
for (const agent of data.connectedAgents) {
|
|
649
|
-
dispatch({ type: "SET_AGENT_CONNECTION", agentId: agent.id, connected: true });
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
645
|
break;
|
|
653
646
|
|
|
654
647
|
case "openclaw.disconnected":
|
|
@@ -735,7 +728,6 @@ export default function App() {
|
|
|
735
728
|
// Skip messages for sessions we're not viewing — they'll be loaded
|
|
736
729
|
// from the server when the user navigates to that session.
|
|
737
730
|
if (!isCurrentSession(sessionKey)) break;
|
|
738
|
-
const agentId = (msg as { agentId?: string }).agentId;
|
|
739
731
|
const chatMsg: ChatMessage = {
|
|
740
732
|
id: randomUUID(),
|
|
741
733
|
sender: "agent",
|
|
@@ -743,10 +735,6 @@ export default function App() {
|
|
|
743
735
|
timestamp: Date.now(),
|
|
744
736
|
threadId,
|
|
745
737
|
encrypted: !!msg.encrypted,
|
|
746
|
-
...(agentId ? {
|
|
747
|
-
senderAgentId: agentId,
|
|
748
|
-
senderAgentName: stateRef.current.v2Agents.find((a) => a.id === agentId)?.name ?? agentId,
|
|
749
|
-
} : {}),
|
|
750
738
|
};
|
|
751
739
|
if (threadId && sessionKey) {
|
|
752
740
|
dispatch({ type: "ADD_THREAD_MESSAGE", message: chatMsg });
|
|
@@ -896,14 +884,6 @@ export default function App() {
|
|
|
896
884
|
break;
|
|
897
885
|
}
|
|
898
886
|
|
|
899
|
-
case "auth.ok": {
|
|
900
|
-
const authData = msg as unknown as { availableAgents?: AgentV2[] };
|
|
901
|
-
if (Array.isArray(authData.availableAgents)) {
|
|
902
|
-
dispatch({ type: "SET_V2_AGENTS", agents: authData.availableAgents });
|
|
903
|
-
}
|
|
904
|
-
break;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
887
|
case "status":
|
|
908
888
|
// Status pings carry the gateway default model, not the per-session model.
|
|
909
889
|
// model.changed is the authoritative source, so we intentionally ignore status.model.
|
|
@@ -1089,6 +1069,7 @@ export default function App() {
|
|
|
1089
1069
|
handleDefaultModelChange={handleDefaultModelChange}
|
|
1090
1070
|
handleSelectJob={handleSelectJob}
|
|
1091
1071
|
/>
|
|
1072
|
+
<ImageLightbox />
|
|
1092
1073
|
</AppDispatchContext.Provider>
|
|
1093
1074
|
</AppStateContext.Provider>
|
|
1094
1075
|
);
|
|
@@ -1196,6 +1177,8 @@ export default function App() {
|
|
|
1196
1177
|
<DebugLogPanel />
|
|
1197
1178
|
</div>
|
|
1198
1179
|
|
|
1180
|
+
<ImageLightbox />
|
|
1181
|
+
|
|
1199
1182
|
{/* Settings modal */}
|
|
1200
1183
|
{showSettings && (
|
|
1201
1184
|
<div
|
|
@@ -1247,17 +1230,6 @@ export default function App() {
|
|
|
1247
1230
|
>
|
|
1248
1231
|
Connection
|
|
1249
1232
|
</button>
|
|
1250
|
-
<button
|
|
1251
|
-
className="pb-2 text-caption font-bold transition-colors"
|
|
1252
|
-
style={{
|
|
1253
|
-
color: settingsTab === "agents" ? "var(--text-primary)" : "var(--text-muted)",
|
|
1254
|
-
borderBottom: settingsTab === "agents" ? "2px solid var(--bg-active)" : "2px solid transparent",
|
|
1255
|
-
marginBottom: "-1px",
|
|
1256
|
-
}}
|
|
1257
|
-
onClick={() => setSettingsTab("agents")}
|
|
1258
|
-
>
|
|
1259
|
-
Agents
|
|
1260
|
-
</button>
|
|
1261
1233
|
<button
|
|
1262
1234
|
className="pb-2 text-caption font-bold transition-colors"
|
|
1263
1235
|
style={{
|
|
@@ -1320,21 +1292,6 @@ export default function App() {
|
|
|
1320
1292
|
<ConnectionSettings />
|
|
1321
1293
|
)}
|
|
1322
1294
|
|
|
1323
|
-
{settingsTab === "agents" && (
|
|
1324
|
-
<div className="space-y-3">
|
|
1325
|
-
<p className="text-caption" style={{ color: "var(--text-secondary)" }}>
|
|
1326
|
-
Manage your AI agents. Each agent has a type (engine) and role (persona).
|
|
1327
|
-
</p>
|
|
1328
|
-
<button
|
|
1329
|
-
onClick={() => setShowAgentSettings(true)}
|
|
1330
|
-
className="w-full py-2.5 text-caption font-medium rounded-lg"
|
|
1331
|
-
style={{ background: "var(--accent-blue)", color: "#fff" }}
|
|
1332
|
-
>
|
|
1333
|
-
Open Agent Manager
|
|
1334
|
-
</button>
|
|
1335
|
-
</div>
|
|
1336
|
-
)}
|
|
1337
|
-
|
|
1338
1295
|
{settingsTab === "security" && (
|
|
1339
1296
|
<E2ESettings />
|
|
1340
1297
|
)}
|
|
@@ -1356,10 +1313,6 @@ export default function App() {
|
|
|
1356
1313
|
</div>
|
|
1357
1314
|
)}
|
|
1358
1315
|
|
|
1359
|
-
{showAgentSettings && (
|
|
1360
|
-
<AgentSettings onClose={() => setShowAgentSettings(false)} />
|
|
1361
|
-
)}
|
|
1362
|
-
|
|
1363
1316
|
</AppDispatchContext.Provider>
|
|
1364
1317
|
</AppStateContext.Provider>
|
|
1365
1318
|
);
|
package/packages/web/src/api.ts
CHANGED
|
@@ -143,6 +143,8 @@ export const authApi = {
|
|
|
143
143
|
/** Sign in with any Firebase provider (Google, GitHub, etc.) */
|
|
144
144
|
firebase: (idToken: string) =>
|
|
145
145
|
request<AuthResponse>("POST", "/auth/firebase", { idToken }),
|
|
146
|
+
/** Demo login — no credentials required */
|
|
147
|
+
demo: () => request<AuthResponse>("POST", "/demo/login"),
|
|
146
148
|
me: () => request<{ id: string; email: string; displayName: string | null; settings: UserSettings }>("GET", "/me"),
|
|
147
149
|
deleteAccount: () => request<{ ok: boolean }>("DELETE", "/auth/account"),
|
|
148
150
|
};
|
|
@@ -182,7 +184,7 @@ export type Channel = {
|
|
|
182
184
|
id: string;
|
|
183
185
|
name: string;
|
|
184
186
|
description: string;
|
|
185
|
-
|
|
187
|
+
openclawAgentId: string;
|
|
186
188
|
systemPrompt: string;
|
|
187
189
|
createdAt: number;
|
|
188
190
|
updatedAt: number;
|
|
@@ -191,7 +193,7 @@ export type Channel = {
|
|
|
191
193
|
export const channelsApi = {
|
|
192
194
|
list: () => request<{ channels: Channel[] }>("GET", "/channels"),
|
|
193
195
|
get: (id: string) => request<Channel>("GET", `/channels/${id}`),
|
|
194
|
-
create: (data: { name: string; description?: string; systemPrompt?: string;
|
|
196
|
+
create: (data: { name: string; description?: string; systemPrompt?: string; openclawAgentId?: string }) =>
|
|
195
197
|
request<Channel>("POST", "/channels", data),
|
|
196
198
|
update: (id: string, data: Partial<Pick<Channel, "name" | "description" | "systemPrompt">>) =>
|
|
197
199
|
request<{ ok: boolean }>("PATCH", `/channels/${id}`, data),
|
|
@@ -223,7 +225,7 @@ export type Task = {
|
|
|
223
225
|
id: string;
|
|
224
226
|
name: string;
|
|
225
227
|
kind: "background" | "adhoc";
|
|
226
|
-
|
|
228
|
+
openclawCronJobId: string | null;
|
|
227
229
|
schedule: string | null;
|
|
228
230
|
instructions: string | null;
|
|
229
231
|
model: string | null;
|
|
@@ -339,61 +341,3 @@ export const setupApi = {
|
|
|
339
341
|
"/setup/cloud-url",
|
|
340
342
|
),
|
|
341
343
|
};
|
|
342
|
-
|
|
343
|
-
// ---- v2 Agents (multi-agent architecture) ----
|
|
344
|
-
export type AgentV2 = {
|
|
345
|
-
id: string;
|
|
346
|
-
name: string;
|
|
347
|
-
type: "openclaw" | "cursor_cli" | "cursor_cloud" | "claude_code" | "mock";
|
|
348
|
-
role: string;
|
|
349
|
-
systemPrompt: string;
|
|
350
|
-
skills: Array<{ name: string; description: string }>;
|
|
351
|
-
capabilities: string[];
|
|
352
|
-
status: "connected" | "disconnected";
|
|
353
|
-
lastConnectedAt: number | null;
|
|
354
|
-
createdAt: number;
|
|
355
|
-
updatedAt: number;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
export const agentsV2Api = {
|
|
359
|
-
list: () => request<{ agents: AgentV2[] }>("GET", "/v2/agents"),
|
|
360
|
-
create: (data: { name: string; type: string; role?: string; systemPrompt?: string; skills?: Array<{ name: string; description: string }>; pairingToken?: string; apiKey?: string; config?: Record<string, unknown> }) =>
|
|
361
|
-
request<AgentV2>("POST", "/v2/agents", data),
|
|
362
|
-
update: (id: string, data: { name?: string; role?: string; systemPrompt?: string; skills?: Array<{ name: string; description: string }>; config?: Record<string, unknown> }) =>
|
|
363
|
-
request<AgentV2>("PATCH", `/v2/agents/${id}`, data),
|
|
364
|
-
delete: (id: string) => request<{ ok: boolean }>("DELETE", `/v2/agents/${id}`),
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// ---- v2 History Query ----
|
|
368
|
-
export type MessageV2 = {
|
|
369
|
-
id: string;
|
|
370
|
-
sender: "user" | "agent";
|
|
371
|
-
senderAgentId?: string;
|
|
372
|
-
senderAgentName?: string;
|
|
373
|
-
targetAgentId?: string;
|
|
374
|
-
text: string;
|
|
375
|
-
mediaUrl?: string;
|
|
376
|
-
encrypted: boolean;
|
|
377
|
-
timestamp: number;
|
|
378
|
-
traces?: Array<{
|
|
379
|
-
verboseLevel: number;
|
|
380
|
-
traceType: string;
|
|
381
|
-
content: string;
|
|
382
|
-
metadata?: Record<string, unknown>;
|
|
383
|
-
}>;
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
export const historyV2Api = {
|
|
387
|
-
query: (params: { sessionKey: string; verboseLevel?: number; limit?: number; agentIdFilter?: string }) => {
|
|
388
|
-
const qs = new URLSearchParams();
|
|
389
|
-
qs.set("sessionKey", params.sessionKey);
|
|
390
|
-
if (params.verboseLevel) qs.set("verboseLevel", String(params.verboseLevel));
|
|
391
|
-
if (params.limit) qs.set("limit", String(params.limit));
|
|
392
|
-
if (params.agentIdFilter) qs.set("agentIdFilter", params.agentIdFilter);
|
|
393
|
-
return request<{ messages: MessageV2[]; hasMore: boolean }>("GET", `/v2/messages/query?${qs.toString()}`);
|
|
394
|
-
},
|
|
395
|
-
traces: (messageId: string, verboseLevel?: number) => {
|
|
396
|
-
const qs = verboseLevel ? `?verboseLevel=${verboseLevel}` : "";
|
|
397
|
-
return request<{ traces: Array<{ id: string; verboseLevel: number; traceType: string; content: string; metadata?: Record<string, unknown>; agentId: string; timestamp: number }> }>("GET", `/v2/messages/traces/${messageId}${qs}`);
|
|
398
|
-
},
|
|
399
|
-
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useRef, useEffect, useState, useMemo, useCallback } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
2
3
|
import { useAppState, useAppDispatch, type ChatMessage, type ActivityItem } from "../store";
|
|
3
4
|
import type { WSMessage } from "../ws";
|
|
4
5
|
import { MessageContent } from "./MessageContent";
|
|
@@ -915,7 +916,7 @@ export function ChatWindow({ sendMessage }: ChatWindowProps) {
|
|
|
915
916
|
placeholder={
|
|
916
917
|
state.openclawConnected
|
|
917
918
|
? `Message #${channelName}`
|
|
918
|
-
: "
|
|
919
|
+
: "OpenClaw is offline…"
|
|
919
920
|
}
|
|
920
921
|
disabled={!state.openclawConnected}
|
|
921
922
|
rows={1}
|
|
@@ -1105,11 +1106,9 @@ function MessageRow({
|
|
|
1105
1106
|
onStop?: () => void;
|
|
1106
1107
|
}) {
|
|
1107
1108
|
const state = useAppState();
|
|
1108
|
-
const
|
|
1109
|
-
const senderLabel = msg.sender === "user" ? "You" : agentName;
|
|
1110
|
-
const AGENT_COLORS: Record<string, string> = { openclaw: "#2BAC76", cursor_cli: "#3B82F6", cursor_cloud: "#6366F1", claude_code: "#F59E0B" };
|
|
1109
|
+
const senderLabel = msg.sender === "user" ? "You" : "OpenClaw Agent";
|
|
1111
1110
|
const avatarColor = msg.sender === "user" ? "#9B59B6" : "#2BAC76";
|
|
1112
|
-
const initial = msg.sender === "user" ? "U" :
|
|
1111
|
+
const initial = msg.sender === "user" ? "U" : "A";
|
|
1113
1112
|
const replyCount = state.threadReplyCounts[msg.id] ?? 0;
|
|
1114
1113
|
|
|
1115
1114
|
// Long-press context menu for mobile
|
|
@@ -1295,10 +1294,10 @@ function MessageRow({
|
|
|
1295
1294
|
} />
|
|
1296
1295
|
</div>
|
|
1297
1296
|
|
|
1298
|
-
{/* Mobile: Long-press context menu (bottom sheet) */}
|
|
1299
|
-
{showContextMenu && (
|
|
1297
|
+
{/* Mobile: Long-press context menu (bottom sheet) — portal to body to escape scroll stacking context on iOS */}
|
|
1298
|
+
{showContextMenu && createPortal(
|
|
1300
1299
|
<div
|
|
1301
|
-
className="fixed inset-0 z-
|
|
1300
|
+
className="fixed inset-0 z-[9999] flex items-end justify-center"
|
|
1302
1301
|
style={{ background: "rgba(0,0,0,0.4)" }}
|
|
1303
1302
|
onClick={() => setShowContextMenu(false)}
|
|
1304
1303
|
>
|
|
@@ -1344,7 +1343,8 @@ function MessageRow({
|
|
|
1344
1343
|
Cancel
|
|
1345
1344
|
</button>
|
|
1346
1345
|
</div>
|
|
1347
|
-
</div
|
|
1346
|
+
</div>,
|
|
1347
|
+
document.body,
|
|
1348
1348
|
)}
|
|
1349
1349
|
</div>
|
|
1350
1350
|
);
|
|
@@ -660,7 +660,7 @@ function CronInfoAndContent({
|
|
|
660
660
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
|
661
661
|
<InfoField label="Cron Job ID">
|
|
662
662
|
<span className="text-caption font-mono" style={{ color: "var(--text-secondary)" }}>
|
|
663
|
-
{task.
|
|
663
|
+
{task.openclawCronJobId ?? "N/A"}
|
|
664
664
|
</span>
|
|
665
665
|
</InfoField>
|
|
666
666
|
|