@hienlh/ppm 0.8.49 → 0.8.50
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/CHANGELOG.md +10 -0
- package/dist/web/assets/api-settings-DfTIjsPW.js +1 -0
- package/dist/web/assets/{chat-tab-CkVy9ut7.js → chat-tab-BZSpI1_2.js} +2 -2
- package/dist/web/assets/{code-editor-CdiHsvVd.js → code-editor-DB-y8tPy.js} +1 -1
- package/dist/web/assets/{database-viewer-DCB3fyHi.js → database-viewer-D4JQEtMD.js} +1 -1
- package/dist/web/assets/{diff-viewer-DZEXDwGs.js → diff-viewer-ToD0FLsL.js} +1 -1
- package/dist/web/assets/{git-graph-BfgY255b.js → git-graph-Dg7SVF-R.js} +1 -1
- package/dist/web/assets/index-DdLIa98_.js +28 -0
- package/dist/web/assets/index-XRJa3Ncz.css +2 -0
- package/dist/web/assets/keybindings-store-BofWHLIC.js +1 -0
- package/dist/web/assets/{markdown-renderer-F1beFJEy.js → markdown-renderer-DMHeWMgi.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CxO5FaWj.js → postgres-viewer-bUYwSwrp.js} +1 -1
- package/dist/web/assets/{settings-store-xG6mKqkD.js → settings-store-ChwdK0tt.js} +2 -2
- package/dist/web/assets/settings-tab-D2bgiL7t.js +1 -0
- package/dist/web/assets/{sqlite-viewer-CQNRFUxH.js → sqlite-viewer-BLUoWIZ5.js} +1 -1
- package/dist/web/assets/{terminal-tab-CEvaCyVU.js → terminal-tab-B5sI9TDZ.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DlFSiqvG.js → use-monaco-theme-0hXmt0_2.js} +1 -1
- package/dist/web/index.html +4 -4
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/server/routes/accounts.ts +137 -2
- package/src/web/components/settings/accounts-settings-section.tsx +255 -6
- package/src/web/lib/api-settings.ts +33 -0
- package/test-tokens.mjs +212 -0
- package/dist/web/assets/api-settings-CaKDC7_s.js +0 -1
- package/dist/web/assets/index-DubLYgN1.css +0 -2
- package/dist/web/assets/index-odr3ymlS.js +0 -28
- package/dist/web/assets/keybindings-store-tyvdfWMV.js +0 -1
- package/dist/web/assets/settings-tab-DLtrBBV2.js +0 -1
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"b7fed017a8dd7ca50bdce37817482823","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DC-bdPS3.js"},{"revision":null,"url":"assets/use-monaco-theme-DlFSiqvG.js"},{"revision":null,"url":"assets/terminal-tab-CEvaCyVU.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-DJUYe5BQ.js"},{"revision":null,"url":"assets/table-B6neW6Hr.js"},{"revision":null,"url":"assets/tab-store-NOBndc0_.js"},{"revision":null,"url":"assets/sqlite-viewer-CQNRFUxH.js"},{"revision":null,"url":"assets/settings-tab-DLtrBBV2.js"},{"revision":null,"url":"assets/settings-store-xG6mKqkD.js"},{"revision":null,"url":"assets/react-rgzL83kk.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-CxO5FaWj.js"},{"revision":null,"url":"assets/markdown-renderer-F1beFJEy.js"},{"revision":null,"url":"assets/keybindings-store-tyvdfWMV.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-CE3bFwLk.js"},{"revision":null,"url":"assets/index-odr3ymlS.js"},{"revision":null,"url":"assets/index-DubLYgN1.css"},{"revision":null,"url":"assets/git-graph-BfgY255b.js"},{"revision":null,"url":"assets/dist-QgqOdSYG.js"},{"revision":null,"url":"assets/diff-viewer-DZEXDwGs.js"},{"revision":null,"url":"assets/database-viewer-DCB3fyHi.js"},{"revision":null,"url":"assets/columns-2-BZ5wv2wA.js"},{"revision":null,"url":"assets/code-editor-CdiHsvVd.js"},{"revision":null,"url":"assets/chat-tab-CkVy9ut7.js"},{"revision":null,"url":"assets/api-settings-CaKDC7_s.js"},{"revision":null,"url":"assets/api-client-TUmacMRS.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"49865f1dc6c4195305930e2e5f60893d","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DC-bdPS3.js"},{"revision":null,"url":"assets/use-monaco-theme-0hXmt0_2.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-B5sI9TDZ.js"},{"revision":null,"url":"assets/tag-DJUYe5BQ.js"},{"revision":null,"url":"assets/table-B6neW6Hr.js"},{"revision":null,"url":"assets/tab-store-NOBndc0_.js"},{"revision":null,"url":"assets/sqlite-viewer-BLUoWIZ5.js"},{"revision":null,"url":"assets/settings-tab-D2bgiL7t.js"},{"revision":null,"url":"assets/settings-store-ChwdK0tt.js"},{"revision":null,"url":"assets/react-rgzL83kk.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-bUYwSwrp.js"},{"revision":null,"url":"assets/markdown-renderer-DMHeWMgi.js"},{"revision":null,"url":"assets/keybindings-store-BofWHLIC.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-CE3bFwLk.js"},{"revision":null,"url":"assets/index-XRJa3Ncz.css"},{"revision":null,"url":"assets/index-DdLIa98_.js"},{"revision":null,"url":"assets/git-graph-Dg7SVF-R.js"},{"revision":null,"url":"assets/dist-QgqOdSYG.js"},{"revision":null,"url":"assets/diff-viewer-ToD0FLsL.js"},{"revision":null,"url":"assets/database-viewer-D4JQEtMD.js"},{"revision":null,"url":"assets/columns-2-BZ5wv2wA.js"},{"revision":null,"url":"assets/code-editor-DB-y8tPy.js"},{"revision":null,"url":"assets/chat-tab-BZSpI1_2.js"},{"revision":null,"url":"assets/api-settings-DfTIjsPW.js"},{"revision":null,"url":"assets/api-client-TUmacMRS.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/package.json
CHANGED
|
@@ -160,9 +160,11 @@ accountsRoutes.post("/oauth/refresh/:id", async (c) => {
|
|
|
160
160
|
/** POST /api/accounts/export — download password-encrypted accounts backup */
|
|
161
161
|
accountsRoutes.post("/export", async (c) => {
|
|
162
162
|
try {
|
|
163
|
-
const { password, accountIds, includeRefreshToken } = await c.req.json() as {
|
|
163
|
+
const { password, accountIds, includeRefreshToken, refreshBeforeExport } = await c.req.json() as {
|
|
164
|
+
password: string; accountIds?: string[]; includeRefreshToken?: boolean; refreshBeforeExport?: boolean;
|
|
165
|
+
};
|
|
164
166
|
if (!password) return c.json(err("Password required"), 400);
|
|
165
|
-
await accountService.refreshBeforeExport(accountIds);
|
|
167
|
+
if (refreshBeforeExport) await accountService.refreshBeforeExport(accountIds);
|
|
166
168
|
const blob = accountService.exportEncrypted(password, accountIds, includeRefreshToken ?? false);
|
|
167
169
|
c.header("Content-Disposition", "attachment; filename=ppm-accounts-backup.json");
|
|
168
170
|
c.header("Content-Type", "application/json");
|
|
@@ -213,6 +215,139 @@ accountsRoutes.post("/:id/verify", async (c) => {
|
|
|
213
215
|
}
|
|
214
216
|
});
|
|
215
217
|
|
|
218
|
+
/** POST /api/accounts/test-export — simulate export: returns decrypted tokens + current DB tokens for comparison */
|
|
219
|
+
accountsRoutes.post("/test-export", async (c) => {
|
|
220
|
+
try {
|
|
221
|
+
const { accountIds, includeRefreshToken } = await c.req.json() as { accountIds: string[]; includeRefreshToken?: boolean };
|
|
222
|
+
if (!accountIds?.length) return c.json(err("accountIds required"), 400);
|
|
223
|
+
|
|
224
|
+
// Snapshot current tokens BEFORE export (export calls refreshBeforeExport which may change tokens)
|
|
225
|
+
const preExportTokens = accountIds.map((id) => {
|
|
226
|
+
const acc = accountService.getWithTokens(id);
|
|
227
|
+
if (!acc) return null;
|
|
228
|
+
return { id, label: acc.label, email: acc.email, accessToken: acc.accessToken, expiresAt: acc.expiresAt };
|
|
229
|
+
}).filter(Boolean) as { id: string; label: string | null; email: string | null; accessToken: string; expiresAt: number | null }[];
|
|
230
|
+
|
|
231
|
+
// Do export with a temp password, then decrypt to get exported tokens
|
|
232
|
+
const tmpPwd = "test-export-" + Date.now();
|
|
233
|
+
await accountService.refreshBeforeExport(accountIds);
|
|
234
|
+
const blob = accountService.exportEncrypted(tmpPwd, accountIds, includeRefreshToken ?? false);
|
|
235
|
+
|
|
236
|
+
// Decrypt to get raw exported tokens
|
|
237
|
+
const { decryptWithPassword } = await import("../../lib/account-crypto.ts");
|
|
238
|
+
const rows = JSON.parse(decryptWithPassword(blob, tmpPwd)) as { id: string; label: string; email: string; access_token: string; expires_at: number | null }[];
|
|
239
|
+
|
|
240
|
+
// Get post-export current tokens (may differ if refreshBeforeExport changed them)
|
|
241
|
+
const postExportTokens = accountIds.map((id) => {
|
|
242
|
+
const acc = accountService.getWithTokens(id);
|
|
243
|
+
if (!acc) return null;
|
|
244
|
+
return { id, label: acc.label, email: acc.email, accessToken: acc.accessToken, expiresAt: acc.expiresAt };
|
|
245
|
+
}).filter(Boolean) as { id: string; label: string | null; email: string | null; accessToken: string; expiresAt: number | null }[];
|
|
246
|
+
|
|
247
|
+
const result = rows.map((row) => {
|
|
248
|
+
const pre = preExportTokens.find((t) => t.id === row.id);
|
|
249
|
+
const post = postExportTokens.find((t) => t.id === row.id);
|
|
250
|
+
return {
|
|
251
|
+
id: row.id,
|
|
252
|
+
label: row.label,
|
|
253
|
+
email: row.email,
|
|
254
|
+
preExportToken: pre?.accessToken ? pre.accessToken.slice(0, 20) + "..." : null,
|
|
255
|
+
preExportTokenFull: pre?.accessToken ?? null,
|
|
256
|
+
exportedToken: row.access_token.slice(0, 20) + "...",
|
|
257
|
+
exportedTokenFull: row.access_token,
|
|
258
|
+
postExportToken: post?.accessToken ? post.accessToken.slice(0, 20) + "..." : null,
|
|
259
|
+
postExportTokenFull: post?.accessToken ?? null,
|
|
260
|
+
preExportExpires: pre?.expiresAt ?? null,
|
|
261
|
+
exportedExpires: row.expires_at,
|
|
262
|
+
postExportExpires: post?.expiresAt ?? null,
|
|
263
|
+
tokenChanged: pre?.accessToken !== post?.accessToken,
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
return c.json(ok(result));
|
|
267
|
+
} catch (e) {
|
|
268
|
+
return c.json(err((e as Error).message), 400);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
/** POST /api/accounts/test-raw-token — test an arbitrary access token against profile API */
|
|
273
|
+
accountsRoutes.post("/test-raw-token", async (c) => {
|
|
274
|
+
const { token } = await c.req.json<{ token: string }>();
|
|
275
|
+
if (!token) return c.json(err("token required"), 400);
|
|
276
|
+
try {
|
|
277
|
+
const res = await fetch("https://api.anthropic.com/api/oauth/profile", {
|
|
278
|
+
headers: {
|
|
279
|
+
Accept: "application/json",
|
|
280
|
+
Authorization: `Bearer ${token}`,
|
|
281
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
282
|
+
"User-Agent": "ppm/1.0",
|
|
283
|
+
},
|
|
284
|
+
signal: AbortSignal.timeout(10_000),
|
|
285
|
+
});
|
|
286
|
+
if (res.status === 200) return c.json(ok({ status: "valid", code: 200 }));
|
|
287
|
+
if (res.status === 429) return c.json(ok({ status: "valid_rate_limited", code: 429 }));
|
|
288
|
+
const body = await res.text().catch(() => "");
|
|
289
|
+
return c.json(ok({ status: "invalid", code: res.status, error: body.slice(0, 300) }));
|
|
290
|
+
} catch (e) {
|
|
291
|
+
return c.json(ok({ status: "error", error: (e as Error).message }));
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
/** POST /api/accounts/:id/test-token — test access token validity + optionally refresh token */
|
|
296
|
+
accountsRoutes.post("/:id/test-token", async (c) => {
|
|
297
|
+
const { id } = c.req.param();
|
|
298
|
+
const { testRefresh } = await c.req.json<{ testRefresh?: boolean }>().catch(() => ({ testRefresh: false }));
|
|
299
|
+
const account = accountService.getWithTokens(id);
|
|
300
|
+
if (!account) return c.json(err("Account not found"), 404);
|
|
301
|
+
|
|
302
|
+
const result: {
|
|
303
|
+
accessToken: { status: string; code?: number; error?: string };
|
|
304
|
+
refreshToken?: { status: string; code?: number; expiresIn?: number; newRefreshToken?: boolean; error?: string };
|
|
305
|
+
} = { accessToken: { status: "unknown" } };
|
|
306
|
+
|
|
307
|
+
// Test access token via profile API
|
|
308
|
+
try {
|
|
309
|
+
const res = await fetch("https://api.anthropic.com/api/oauth/profile", {
|
|
310
|
+
headers: {
|
|
311
|
+
Accept: "application/json",
|
|
312
|
+
Authorization: `Bearer ${account.accessToken}`,
|
|
313
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
314
|
+
"User-Agent": "ppm/1.0",
|
|
315
|
+
},
|
|
316
|
+
signal: AbortSignal.timeout(10_000),
|
|
317
|
+
});
|
|
318
|
+
if (res.status === 200) {
|
|
319
|
+
result.accessToken = { status: "valid", code: 200 };
|
|
320
|
+
} else if (res.status === 429) {
|
|
321
|
+
result.accessToken = { status: "valid_rate_limited", code: 429 };
|
|
322
|
+
} else {
|
|
323
|
+
const body = await res.text().catch(() => "");
|
|
324
|
+
result.accessToken = { status: "invalid", code: res.status, error: body.slice(0, 300) };
|
|
325
|
+
}
|
|
326
|
+
} catch (e) {
|
|
327
|
+
result.accessToken = { status: "error", error: (e as Error).message };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Test refresh token
|
|
331
|
+
if (testRefresh && account.refreshToken) {
|
|
332
|
+
try {
|
|
333
|
+
await accountService.refreshAccessToken(id, false);
|
|
334
|
+
const refreshed = accountService.getWithTokens(id);
|
|
335
|
+
result.refreshToken = {
|
|
336
|
+
status: "valid",
|
|
337
|
+
code: 200,
|
|
338
|
+
expiresIn: refreshed?.expiresAt ? refreshed.expiresAt - Math.floor(Date.now() / 1000) : undefined,
|
|
339
|
+
newRefreshToken: true,
|
|
340
|
+
};
|
|
341
|
+
} catch (e) {
|
|
342
|
+
result.refreshToken = { status: "invalid", error: (e as Error).message };
|
|
343
|
+
}
|
|
344
|
+
} else if (testRefresh && !account.refreshToken) {
|
|
345
|
+
result.refreshToken = { status: "no_token", error: "No refresh token (temporary account)" };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return c.json(ok(result));
|
|
349
|
+
});
|
|
350
|
+
|
|
216
351
|
/** DELETE /api/accounts/:id */
|
|
217
352
|
accountsRoutes.delete("/:id", (c) => {
|
|
218
353
|
const { id } = c.req.param();
|
|
@@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input";
|
|
|
6
6
|
import { Label } from "@/components/ui/label";
|
|
7
7
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
8
8
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
|
|
9
|
-
import { Eye, Loader2, Copy, X, Download, Upload, Lock } from "lucide-react";
|
|
9
|
+
import { Eye, Loader2, Copy, X, Download, Upload, Lock, FlaskConical } from "lucide-react";
|
|
10
10
|
import { getAuthToken } from "../../lib/api-client";
|
|
11
11
|
import {
|
|
12
12
|
getAccounts,
|
|
@@ -20,10 +20,15 @@ import {
|
|
|
20
20
|
updateAccountSettings,
|
|
21
21
|
getAllAccountUsages,
|
|
22
22
|
importAccounts,
|
|
23
|
+
testAccountToken,
|
|
24
|
+
testExport,
|
|
25
|
+
testRawToken,
|
|
23
26
|
type AccountInfo,
|
|
24
27
|
type AccountSettings,
|
|
25
28
|
type AccountUsageEntry,
|
|
26
29
|
type OAuthProfileData,
|
|
30
|
+
type TokenTestResult,
|
|
31
|
+
type ExportedTokenInfo,
|
|
27
32
|
} from "../../lib/api-settings";
|
|
28
33
|
|
|
29
34
|
function detectTokenType(token: string): string {
|
|
@@ -152,6 +157,7 @@ export function AccountsSettingsSection() {
|
|
|
152
157
|
const [exportSelected, setExportSelected] = useState<Set<string>>(new Set());
|
|
153
158
|
const [exporting, setExporting] = useState(false);
|
|
154
159
|
const [exportFullTransfer, setExportFullTransfer] = useState(false);
|
|
160
|
+
const [exportRefreshBefore, setExportRefreshBefore] = useState(false);
|
|
155
161
|
|
|
156
162
|
// Import dialog
|
|
157
163
|
const [showImportDialog, setShowImportDialog] = useState(false);
|
|
@@ -160,6 +166,16 @@ export function AccountsSettingsSection() {
|
|
|
160
166
|
const [importing, setImporting] = useState(false);
|
|
161
167
|
const [importError, setImportError] = useState<string | null>(null);
|
|
162
168
|
|
|
169
|
+
// Token test dialog
|
|
170
|
+
const [showTokenTest, setShowTokenTest] = useState(false);
|
|
171
|
+
const [tokenTestResults, setTokenTestResults] = useState<Map<string, { loading: boolean; result?: TokenTestResult; error?: string }>>(new Map());
|
|
172
|
+
const [tokenTestRefresh, setTokenTestRefresh] = useState(false);
|
|
173
|
+
// Export simulation — accumulate rounds
|
|
174
|
+
const [exportRounds, setExportRounds] = useState<{ round: number; time: string; includeRefresh: boolean; items: ExportedTokenInfo[] }[]>([]);
|
|
175
|
+
const [exportSimLoading, setExportSimLoading] = useState(false);
|
|
176
|
+
const [exportSimIncludeRefresh, setExportSimIncludeRefresh] = useState(false);
|
|
177
|
+
const [rawTokenTests, setRawTokenTests] = useState<Map<string, { loading: boolean; status?: string; code?: number; error?: string }>>(new Map());
|
|
178
|
+
|
|
163
179
|
useEffect(() => {
|
|
164
180
|
refresh();
|
|
165
181
|
}, []);
|
|
@@ -304,6 +320,7 @@ export function AccountsSettingsSection() {
|
|
|
304
320
|
setExportConfirm("");
|
|
305
321
|
setExportSelected(new Set(exportableAccounts.map((a) => a.id)));
|
|
306
322
|
setExportFullTransfer(false);
|
|
323
|
+
setExportRefreshBefore(false);
|
|
307
324
|
setShowExportDialog(true);
|
|
308
325
|
}
|
|
309
326
|
|
|
@@ -325,7 +342,7 @@ export function AccountsSettingsSection() {
|
|
|
325
342
|
const res = await fetch("/api/accounts/export", {
|
|
326
343
|
method: "POST",
|
|
327
344
|
headers,
|
|
328
|
-
body: JSON.stringify({ password: exportPassword, accountIds: [...exportSelected], includeRefreshToken: exportFullTransfer }),
|
|
345
|
+
body: JSON.stringify({ password: exportPassword, accountIds: [...exportSelected], includeRefreshToken: exportFullTransfer, refreshBeforeExport: exportRefreshBefore }),
|
|
329
346
|
});
|
|
330
347
|
if (!res.ok) { const j = await res.json() as any; throw new Error(j.error ?? `Export failed: ${res.status}`); }
|
|
331
348
|
const text = await res.text();
|
|
@@ -375,6 +392,46 @@ export function AccountsSettingsSection() {
|
|
|
375
392
|
}
|
|
376
393
|
|
|
377
394
|
|
|
395
|
+
async function simulateExport() {
|
|
396
|
+
const oauthIds = accounts.filter((a) => a.hasRefreshToken).map((a) => a.id);
|
|
397
|
+
if (!oauthIds.length) return;
|
|
398
|
+
setExportSimLoading(true);
|
|
399
|
+
try {
|
|
400
|
+
const data = await testExport(oauthIds, exportSimIncludeRefresh);
|
|
401
|
+
const roundNum = exportRounds.length + 1;
|
|
402
|
+
const time = new Date().toLocaleTimeString();
|
|
403
|
+
setExportRounds((prev) => [...prev, { round: roundNum, time, includeRefresh: exportSimIncludeRefresh, items: data }]);
|
|
404
|
+
} catch { /* ignore */ }
|
|
405
|
+
setExportSimLoading(false);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function testRawTokenClick(key: string, token: string) {
|
|
409
|
+
setRawTokenTests((prev) => new Map(prev).set(key, { loading: true }));
|
|
410
|
+
try {
|
|
411
|
+
const result = await testRawToken(token);
|
|
412
|
+
setRawTokenTests((prev) => new Map(prev).set(key, { loading: false, ...result }));
|
|
413
|
+
} catch (e) {
|
|
414
|
+
setRawTokenTests((prev) => new Map(prev).set(key, { loading: false, status: "error", error: (e as Error).message }));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function runTokenTest(id: string) {
|
|
419
|
+
setTokenTestResults((prev) => new Map(prev).set(id, { loading: true }));
|
|
420
|
+
try {
|
|
421
|
+
const result = await testAccountToken(id, tokenTestRefresh);
|
|
422
|
+
setTokenTestResults((prev) => new Map(prev).set(id, { loading: false, result }));
|
|
423
|
+
} catch (e) {
|
|
424
|
+
setTokenTestResults((prev) => new Map(prev).set(id, { loading: false, error: (e as Error).message }));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function runAllTokenTests() {
|
|
429
|
+
const oauthAccounts = accounts.filter((a) => a.expiresAt !== null);
|
|
430
|
+
for (const acc of oauthAccounts) {
|
|
431
|
+
runTokenTest(acc.id);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
378
435
|
const tokenHint = newToken.trim() ? detectTokenType(newToken.trim()) : "";
|
|
379
436
|
|
|
380
437
|
return (
|
|
@@ -442,7 +499,7 @@ export function AccountsSettingsSection() {
|
|
|
442
499
|
})}
|
|
443
500
|
</div>
|
|
444
501
|
|
|
445
|
-
<div className="grid grid-cols-3
|
|
502
|
+
<div className={`grid gap-1.5 ${import.meta.env.DEV ? "grid-cols-4" : "grid-cols-3"}`}>
|
|
446
503
|
<Button size="sm" className="h-8 text-xs cursor-pointer" onClick={() => setShowAddDialog(true)}>
|
|
447
504
|
+ Add
|
|
448
505
|
</Button>
|
|
@@ -452,6 +509,11 @@ export function AccountsSettingsSection() {
|
|
|
452
509
|
<Button size="sm" variant="outline" className="h-8 text-xs cursor-pointer" onClick={() => openImportDialog()}>
|
|
453
510
|
<Upload className="size-3.5 mr-1" /> Import
|
|
454
511
|
</Button>
|
|
512
|
+
{import.meta.env.DEV && (
|
|
513
|
+
<Button size="sm" variant="outline" className="h-8 text-xs cursor-pointer" onClick={() => { setTokenTestResults(new Map()); setTokenTestRefresh(false); setExportRounds([]); setRawTokenTests(new Map()); setExportSimIncludeRefresh(false); setShowTokenTest(true); }}>
|
|
514
|
+
<FlaskConical className="size-3.5 mr-1" /> Test
|
|
515
|
+
</Button>
|
|
516
|
+
)}
|
|
455
517
|
</div>
|
|
456
518
|
</div>
|
|
457
519
|
|
|
@@ -715,6 +777,19 @@ export function AccountsSettingsSection() {
|
|
|
715
777
|
Include refresh tokens (full transfer)
|
|
716
778
|
</label>
|
|
717
779
|
</div>
|
|
780
|
+
{/* Refresh before export toggle */}
|
|
781
|
+
<div className="flex items-center gap-2">
|
|
782
|
+
<input
|
|
783
|
+
type="checkbox"
|
|
784
|
+
id="export-refresh-before"
|
|
785
|
+
checked={exportRefreshBefore}
|
|
786
|
+
onChange={(e) => setExportRefreshBefore(e.target.checked)}
|
|
787
|
+
className="size-3.5 accent-primary cursor-pointer"
|
|
788
|
+
/>
|
|
789
|
+
<label htmlFor="export-refresh-before" className="text-[11px] cursor-pointer">
|
|
790
|
+
Refresh tokens before export
|
|
791
|
+
</label>
|
|
792
|
+
</div>
|
|
718
793
|
{exportFullTransfer ? (
|
|
719
794
|
<div className="rounded-md border border-red-500/30 bg-red-500/5 p-2.5 space-y-1">
|
|
720
795
|
<p className="text-[10px] font-medium text-red-600">Full transfer — source accounts will expire</p>
|
|
@@ -722,11 +797,18 @@ export function AccountsSettingsSection() {
|
|
|
722
797
|
Refresh tokens are included. Once the target machine refreshes, <strong>accounts on this machine will only work for ~1h</strong> then become temporary. Use this only to move accounts to another machine.
|
|
723
798
|
</p>
|
|
724
799
|
</div>
|
|
725
|
-
) : (
|
|
800
|
+
) : exportRefreshBefore ? (
|
|
726
801
|
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 p-2.5 space-y-1">
|
|
727
|
-
<p className="text-[10px] font-medium text-amber-600">
|
|
802
|
+
<p className="text-[10px] font-medium text-amber-600">Refresh before export — invalidates previous shares</p>
|
|
803
|
+
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
|
804
|
+
Tokens will be refreshed to maximize validity (~1h). But <strong>any previously shared tokens will be invalidated</strong> because Anthropic only allows 1 active access token per account.
|
|
805
|
+
</p>
|
|
806
|
+
</div>
|
|
807
|
+
) : (
|
|
808
|
+
<div className="rounded-md border border-green-500/30 bg-green-500/5 p-2.5 space-y-1">
|
|
809
|
+
<p className="text-[10px] font-medium text-green-600">Share current token (default, safe)</p>
|
|
728
810
|
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
|
729
|
-
|
|
811
|
+
Exports the current access token as-is. Host and target share the same token. No invalidation. Token validity = remaining time until next auto-refresh.
|
|
730
812
|
</p>
|
|
731
813
|
</div>
|
|
732
814
|
)}
|
|
@@ -776,6 +858,173 @@ export function AccountsSettingsSection() {
|
|
|
776
858
|
</DialogContent>
|
|
777
859
|
</Dialog>
|
|
778
860
|
|
|
861
|
+
{/* Token test dialog */}
|
|
862
|
+
<Dialog open={showTokenTest} onOpenChange={(v) => { if (!v) setShowTokenTest(false); }}>
|
|
863
|
+
<DialogContent className="sm:max-w-xl max-h-[85vh] flex flex-col">
|
|
864
|
+
<DialogHeader>
|
|
865
|
+
<DialogTitle className="text-sm flex items-center gap-1.5"><FlaskConical className="size-3.5" /> Token Test</DialogTitle>
|
|
866
|
+
<DialogDescription className="text-xs">Test tokens & simulate export to compare token validity.</DialogDescription>
|
|
867
|
+
</DialogHeader>
|
|
868
|
+
<div className="space-y-3 overflow-y-auto flex-1 pr-1">
|
|
869
|
+
{/* Section 1: Quick test current DB tokens */}
|
|
870
|
+
<div className="space-y-2">
|
|
871
|
+
<div className="flex items-center justify-between">
|
|
872
|
+
<p className="text-[11px] font-medium">Current Tokens (DB)</p>
|
|
873
|
+
<Button size="sm" variant="outline" className="h-6 text-[10px] cursor-pointer" onClick={runAllTokenTests}>
|
|
874
|
+
Test All
|
|
875
|
+
</Button>
|
|
876
|
+
</div>
|
|
877
|
+
<div className="space-y-1.5">
|
|
878
|
+
{accounts.map((acc) => {
|
|
879
|
+
const entry = tokenTestResults.get(acc.id);
|
|
880
|
+
return (
|
|
881
|
+
<div key={acc.id} className="p-2 rounded border bg-card space-y-1">
|
|
882
|
+
<div className="flex items-center justify-between gap-2">
|
|
883
|
+
<div className="min-w-0 flex-1">
|
|
884
|
+
<span className="text-[11px] font-medium truncate block">{acc.label ?? acc.email ?? acc.id.slice(0, 8)}</span>
|
|
885
|
+
{acc.expiresAt && (
|
|
886
|
+
<span className="text-[10px] text-muted-foreground">
|
|
887
|
+
{acc.expiresAt > Math.floor(Date.now() / 1000)
|
|
888
|
+
? `${Math.floor((acc.expiresAt - Math.floor(Date.now() / 1000)) / 60)}m left`
|
|
889
|
+
: `expired ${Math.floor((Math.floor(Date.now() / 1000) - acc.expiresAt) / 60)}m ago`}
|
|
890
|
+
</span>
|
|
891
|
+
)}
|
|
892
|
+
</div>
|
|
893
|
+
<Button size="sm" variant="outline" className="h-6 text-[10px] cursor-pointer shrink-0" disabled={entry?.loading} onClick={() => runTokenTest(acc.id)}>
|
|
894
|
+
{entry?.loading ? <Loader2 className="size-3 animate-spin" /> : "Test"}
|
|
895
|
+
</Button>
|
|
896
|
+
</div>
|
|
897
|
+
{entry && !entry.loading && (
|
|
898
|
+
<div className="text-[10px] pl-1 border-l-2 border-muted ml-1">
|
|
899
|
+
{entry.error && <p className="text-red-500">{entry.error}</p>}
|
|
900
|
+
{entry.result && (
|
|
901
|
+
<span className={entry.result.accessToken.status.startsWith("valid") ? "text-green-600" : "text-red-500"}>
|
|
902
|
+
{entry.result.accessToken.status} {entry.result.accessToken.code && `(${entry.result.accessToken.code})`}
|
|
903
|
+
</span>
|
|
904
|
+
)}
|
|
905
|
+
</div>
|
|
906
|
+
)}
|
|
907
|
+
</div>
|
|
908
|
+
);
|
|
909
|
+
})}
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
|
|
913
|
+
{/* Section 2: Export simulation */}
|
|
914
|
+
<div className="border-t pt-3 space-y-2">
|
|
915
|
+
<div className="flex items-center justify-between">
|
|
916
|
+
<p className="text-[11px] font-medium">Simulate Export</p>
|
|
917
|
+
{exportRounds.length > 0 && (
|
|
918
|
+
<span className="text-[10px] text-muted-foreground">{exportRounds.length} round(s)</span>
|
|
919
|
+
)}
|
|
920
|
+
</div>
|
|
921
|
+
<p className="text-[10px] text-muted-foreground">
|
|
922
|
+
Each "Run Export" appends a new round. All tokens from every round are kept for comparison.
|
|
923
|
+
</p>
|
|
924
|
+
<div className="flex items-center gap-3">
|
|
925
|
+
<div className="flex items-center gap-1.5">
|
|
926
|
+
<input
|
|
927
|
+
type="checkbox"
|
|
928
|
+
id="sim-include-refresh"
|
|
929
|
+
checked={exportSimIncludeRefresh}
|
|
930
|
+
onChange={(e) => setExportSimIncludeRefresh(e.target.checked)}
|
|
931
|
+
className="size-3 accent-primary cursor-pointer"
|
|
932
|
+
/>
|
|
933
|
+
<label htmlFor="sim-include-refresh" className="text-[10px] cursor-pointer">Include refresh tokens</label>
|
|
934
|
+
</div>
|
|
935
|
+
<Button size="sm" className="h-7 text-[11px] cursor-pointer" disabled={exportSimLoading} onClick={simulateExport}>
|
|
936
|
+
{exportSimLoading ? <><Loader2 className="size-3 animate-spin mr-1" /> Exporting...</> : `Run Export #${exportRounds.length + 1}`}
|
|
937
|
+
</Button>
|
|
938
|
+
</div>
|
|
939
|
+
|
|
940
|
+
{exportRounds.length > 0 && (
|
|
941
|
+
<div className="space-y-3">
|
|
942
|
+
{/* Test All button across all rounds */}
|
|
943
|
+
<Button
|
|
944
|
+
size="sm"
|
|
945
|
+
variant="outline"
|
|
946
|
+
className="w-full h-7 text-[10px] cursor-pointer"
|
|
947
|
+
onClick={() => {
|
|
948
|
+
for (const round of exportRounds) {
|
|
949
|
+
for (const item of round.items) {
|
|
950
|
+
if (item.preExportTokenFull) testRawTokenClick(`r${round.round}-pre-${item.id}`, item.preExportTokenFull);
|
|
951
|
+
if (item.exportedTokenFull) testRawTokenClick(`r${round.round}-exp-${item.id}`, item.exportedTokenFull);
|
|
952
|
+
if (item.postExportTokenFull) testRawTokenClick(`r${round.round}-post-${item.id}`, item.postExportTokenFull);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}}
|
|
956
|
+
>
|
|
957
|
+
Test All Tokens ({exportRounds.reduce((n, r) => n + r.items.length * 3, 0)} tokens)
|
|
958
|
+
</Button>
|
|
959
|
+
|
|
960
|
+
{/* Render each round */}
|
|
961
|
+
{exportRounds.map((round) => (
|
|
962
|
+
<div key={round.round} className="space-y-1.5">
|
|
963
|
+
<div className="flex items-center gap-2">
|
|
964
|
+
<p className="text-[11px] font-medium text-primary">Round #{round.round}</p>
|
|
965
|
+
<span className="text-[9px] text-muted-foreground">{round.time}</span>
|
|
966
|
+
<Badge variant={round.includeRefresh ? "destructive" : "secondary"} className="text-[8px] px-1 py-0">
|
|
967
|
+
{round.includeRefresh ? "with refresh" : "access only"}
|
|
968
|
+
</Badge>
|
|
969
|
+
</div>
|
|
970
|
+
{round.items.map((item) => (
|
|
971
|
+
<div key={`r${round.round}-${item.id}`} className="p-2 rounded-lg border bg-card space-y-1.5">
|
|
972
|
+
<div className="flex items-center gap-2">
|
|
973
|
+
<span className="text-[10px] font-medium">{item.label ?? item.email ?? item.id.slice(0, 8)}</span>
|
|
974
|
+
{item.tokenChanged && <Badge variant="secondary" className="text-[8px] px-1 py-0">DB token changed</Badge>}
|
|
975
|
+
</div>
|
|
976
|
+
{[
|
|
977
|
+
{ key: `r${round.round}-pre-${item.id}`, label: "Pre-export", token: item.preExportTokenFull, preview: item.preExportToken, expires: item.preExportExpires },
|
|
978
|
+
{ key: `r${round.round}-exp-${item.id}`, label: "Exported", token: item.exportedTokenFull, preview: item.exportedToken, expires: item.exportedExpires },
|
|
979
|
+
{ key: `r${round.round}-post-${item.id}`, label: "Post-export", token: item.postExportTokenFull, preview: item.postExportToken, expires: item.postExportExpires },
|
|
980
|
+
].map((row) => {
|
|
981
|
+
const test = rawTokenTests.get(row.key);
|
|
982
|
+
return (
|
|
983
|
+
<div key={row.key} className="flex items-center gap-1.5 text-[10px]">
|
|
984
|
+
<span className="w-20 shrink-0 text-muted-foreground text-[9px]">{row.label}</span>
|
|
985
|
+
<code className="flex-1 truncate font-mono text-[9px] bg-muted px-1 rounded">{row.preview ?? "N/A"}</code>
|
|
986
|
+
{row.expires && (
|
|
987
|
+
<span className="text-[9px] text-muted-foreground shrink-0">
|
|
988
|
+
{row.expires > Math.floor(Date.now() / 1000)
|
|
989
|
+
? `${Math.floor((row.expires - Math.floor(Date.now() / 1000)) / 60)}m`
|
|
990
|
+
: `exp`}
|
|
991
|
+
</span>
|
|
992
|
+
)}
|
|
993
|
+
{row.token ? (
|
|
994
|
+
<Button
|
|
995
|
+
size="sm"
|
|
996
|
+
variant="outline"
|
|
997
|
+
className="h-5 text-[9px] px-1.5 cursor-pointer shrink-0"
|
|
998
|
+
disabled={test?.loading}
|
|
999
|
+
onClick={() => testRawTokenClick(row.key, row.token!)}
|
|
1000
|
+
>
|
|
1001
|
+
{test?.loading ? <Loader2 className="size-2.5 animate-spin" /> : "Test"}
|
|
1002
|
+
</Button>
|
|
1003
|
+
) : <span className="text-[9px] text-muted-foreground">-</span>}
|
|
1004
|
+
{test && !test.loading && (
|
|
1005
|
+
<span className={`text-[9px] font-medium shrink-0 ${test.status?.startsWith("valid") ? "text-green-600" : "text-red-500"}`}>
|
|
1006
|
+
{test.status}
|
|
1007
|
+
</span>
|
|
1008
|
+
)}
|
|
1009
|
+
</div>
|
|
1010
|
+
);
|
|
1011
|
+
})}
|
|
1012
|
+
</div>
|
|
1013
|
+
))}
|
|
1014
|
+
</div>
|
|
1015
|
+
))}
|
|
1016
|
+
</div>
|
|
1017
|
+
)}
|
|
1018
|
+
</div>
|
|
1019
|
+
</div>
|
|
1020
|
+
<DialogFooter>
|
|
1021
|
+
<Button size="sm" variant="outline" className="text-xs h-7 cursor-pointer" onClick={() => setShowTokenTest(false)}>
|
|
1022
|
+
Close
|
|
1023
|
+
</Button>
|
|
1024
|
+
</DialogFooter>
|
|
1025
|
+
</DialogContent>
|
|
1026
|
+
</Dialog>
|
|
1027
|
+
|
|
779
1028
|
{/* Import dialog — paste/file data + password */}
|
|
780
1029
|
<Dialog open={showImportDialog} onOpenChange={(v) => { if (!v) setShowImportDialog(false); }}>
|
|
781
1030
|
<DialogContent className="sm:max-w-md">
|
|
@@ -119,6 +119,39 @@ export function importAccounts(params: { data: string; password: string }): Prom
|
|
|
119
119
|
return api.post<{ imported: number; refreshed: number }>("/api/accounts/import", params);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
export interface TokenTestResult {
|
|
123
|
+
accessToken: { status: string; code?: number; error?: string };
|
|
124
|
+
refreshToken?: { status: string; code?: number; expiresIn?: number; newRefreshToken?: boolean; error?: string };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function testAccountToken(id: string, testRefresh = false): Promise<TokenTestResult> {
|
|
128
|
+
return api.post<TokenTestResult>(`/api/accounts/${id}/test-token`, { testRefresh });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ExportedTokenInfo {
|
|
132
|
+
id: string;
|
|
133
|
+
label: string;
|
|
134
|
+
email: string;
|
|
135
|
+
preExportToken: string | null;
|
|
136
|
+
preExportTokenFull: string | null;
|
|
137
|
+
exportedToken: string | null;
|
|
138
|
+
exportedTokenFull: string | null;
|
|
139
|
+
postExportToken: string | null;
|
|
140
|
+
postExportTokenFull: string | null;
|
|
141
|
+
preExportExpires: number | null;
|
|
142
|
+
exportedExpires: number | null;
|
|
143
|
+
postExportExpires: number | null;
|
|
144
|
+
tokenChanged: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function testExport(accountIds: string[], includeRefreshToken = false): Promise<ExportedTokenInfo[]> {
|
|
148
|
+
return api.post<ExportedTokenInfo[]>("/api/accounts/test-export", { accountIds, includeRefreshToken });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function testRawToken(token: string): Promise<{ status: string; code?: number; error?: string }> {
|
|
152
|
+
return api.post<{ status: string; code?: number; error?: string }>("/api/accounts/test-raw-token", { token });
|
|
153
|
+
}
|
|
154
|
+
|
|
122
155
|
export interface AIProviderSettings {
|
|
123
156
|
type?: string;
|
|
124
157
|
api_key_env?: string;
|