@picked/app-kit 0.0.0-stage → 0.0.1-dev.1794447047683
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/LICENSE +1 -0
- package/README.md +186 -2
- package/bin/copy-frame.mjs +23 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cjs/frame.js +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/node.js +1 -0
- package/dist/cjs/oauth.js +56 -0
- package/dist/cjs/server.js +1 -0
- package/dist/cjs/testing.js +1 -0
- package/dist/esm/frame.js +132 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/node.js +40 -0
- package/dist/esm/oauth.js +202 -0
- package/dist/esm/server.js +279 -0
- package/dist/esm/testing.js +197 -0
- package/dist/types/client.d.ts +145 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/frame.d.ts +124 -0
- package/dist/types/frame.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/link.d.ts +41 -0
- package/dist/types/link.d.ts.map +1 -0
- package/dist/types/node.d.ts +19 -0
- package/dist/types/node.d.ts.map +1 -0
- package/dist/types/oauth.d.ts +128 -0
- package/dist/types/oauth.d.ts.map +1 -0
- package/dist/types/server.d.ts +167 -0
- package/dist/types/server.d.ts.map +1 -0
- package/dist/types/testing.d.ts +84 -0
- package/dist/types/testing.d.ts.map +1 -0
- package/package.json +98 -4
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@picked/app-protocol");var t=600,n=`picked-app:oauth-result`,r=`picked-app:oauth-start-ready`,i=`picked-app:oauth-start`,a=`/oauth/start`,o=12;async function s(n,r){let i={...r,exp:Math.floor(Date.now()/1e3)+t},a=globalThis.crypto.getRandomValues(new Uint8Array(o)),s=await globalThis.crypto.subtle.encrypt({name:`AES-GCM`,iv:a},await(0,e.deriveSecretKey)(n,`oauth-state`),new TextEncoder().encode(JSON.stringify(i))),c=new Uint8Array(o+s.byteLength);return c.set(a),c.set(new Uint8Array(s),o),(0,e.toBase64url)(c)}async function c(t,n){try{let r=(0,e.fromBase64url)(n),i=await globalThis.crypto.subtle.decrypt({name:`AES-GCM`,iv:r.slice(0,o)},await(0,e.deriveSecretKey)(t,`oauth-state`),r.slice(o));return JSON.parse(new TextDecoder().decode(i))}catch{return null}}async function l(e,t,n){if(t===null||t===``)return null;let r=await c(e,t);if(typeof r!=`object`||!r)return null;let i=e=>Reflect.get(r,e),[a,o,s,l,u,d]=[i(`installationId`),i(`apiUrl`),i(`connection`),i(`exp`),i(`nonce`),i(`sessionToken`)];return typeof a!=`string`||typeof o!=`string`||typeof s!=`string`||typeof l!=`number`||typeof u!=`string`||u!==n||typeof d!=`string`||l<Date.now()/1e3?null:{installationId:a,apiUrl:o,connection:s,exp:l,nonce:u,sessionToken:d}}function u(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}function d(e){return JSON.stringify(e).replaceAll(`<`,String.raw`\u003c`)}function f(e){let t=d({type:n,...e});return`<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head><meta charset="utf-8" /><title>${u(e.message)}</title></head>
|
|
4
|
+
<body style="font: 14px system-ui, sans-serif; padding: 24px">
|
|
5
|
+
<p>${u(e.message)}</p>
|
|
6
|
+
<p id="close" hidden>You can close this window and return to Picked.</p>
|
|
7
|
+
<script>
|
|
8
|
+
if (window.opener) {
|
|
9
|
+
window.opener.postMessage(${t}, window.location.origin)
|
|
10
|
+
window.close()
|
|
11
|
+
} else {
|
|
12
|
+
document.getElementById('close').hidden = false
|
|
13
|
+
}
|
|
14
|
+
<\/script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>`}function p(){return`<!doctype html>
|
|
17
|
+
<html lang="en">
|
|
18
|
+
<head><meta charset="utf-8" /><title>Connecting…</title></head>
|
|
19
|
+
<body style="font: 14px system-ui, sans-serif; padding: 24px">
|
|
20
|
+
<p id="status">Connecting…</p>
|
|
21
|
+
<script>
|
|
22
|
+
const status = document.getElementById('status')
|
|
23
|
+
const fail = (message) => {
|
|
24
|
+
status.textContent = message
|
|
25
|
+
if (!window.opener) return
|
|
26
|
+
window.opener.postMessage({ type: ${d(n)}, ok: false, message }, window.location.origin)
|
|
27
|
+
window.close()
|
|
28
|
+
}
|
|
29
|
+
let started = false
|
|
30
|
+
window.addEventListener('message', async (event) => {
|
|
31
|
+
if (event.origin !== window.location.origin || event.source !== window.opener) return
|
|
32
|
+
const data = event.data
|
|
33
|
+
if (started || !data || data.type !== ${d(i)} || typeof data.sessionToken !== 'string') return
|
|
34
|
+
started = true
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(window.location.pathname, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { authorization: 'Bearer ' + data.sessionToken, 'content-type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(data.params ?? null),
|
|
40
|
+
})
|
|
41
|
+
const answer = await response.json()
|
|
42
|
+
const url = typeof answer.url === 'string' ? new URL(answer.url) : null
|
|
43
|
+
if (!response.ok || url === null || (url.protocol !== 'https:' && url.protocol !== 'http:')) {
|
|
44
|
+
fail(answer.error?.message ?? 'The app answered ' + response.status)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
window.location.replace(url.href)
|
|
48
|
+
} catch {
|
|
49
|
+
fail('The app could not start connecting')
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
if (window.opener) window.opener.postMessage({ type: ${d(r)} }, window.location.origin)
|
|
53
|
+
else status.textContent = 'Start connecting from the app’s page in Picked.'
|
|
54
|
+
<\/script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>`}var m=new Set([`localhost`,`127.0.0.1`,`[::1]`]);function h(e){let t=new URL(e.url),n=e.headers.get(`host`)??t.host,r=e.headers.get(`x-forwarded-proto`)?.split(`,`)[0]?.trim(),i=r===`https`||r===`http`?r:t.protocol.slice(0,-1);try{return new URL(`${i}://${n}`)}catch{return t}}function g(e){let t=h(e);return t.protocol===`http:`&&m.has(t.hostname)?{name:`picked-oauth-${t.port||`80`}`,attributes:`Path=/; HttpOnly; SameSite=Lax`}:{name:`__Host-picked-oauth`,attributes:`Path=/; HttpOnly; SameSite=Lax; Secure`}}function _(e,t){for(let n of(e.headers.get(`cookie`)??``).split(`;`)){let e=n.indexOf(`=`);if(e!==-1&&n.slice(0,e).trim()===t)return n.slice(e+1).trim()}return null}var v={"cache-control":`no-store`,"x-content-type-options":`nosniff`};function y(e,t={}){return new Response(e,{headers:{...v,"content-type":`text/html; charset=utf-8`,"content-security-policy":`frame-ancestors 'none'`,"referrer-policy":`no-referrer`,...t}})}function b(e,t,n={}){return new Response(JSON.stringify(e),{status:t,headers:{...v,"content-type":`application/json`,...n}})}async function x(n,r){if(n.method===`GET`)return y(p());if(n.method!==`POST`)return b({error:{message:`Method not allowed`}},405,{allow:`GET, POST`});let i=n.headers.get(`authorization`)??``,a=i.startsWith(`Bearer `)?i.slice(7):``,o=a===``?null:await(0,e.verifySessionToken)(r.pickedKeys,a);if(o===null)return b({error:{message:`Sign in again`}},401);let c=await n.json().catch(()=>null),l=globalThis.crypto.randomUUID(),u=await r.authorize({session:o,sessionToken:a,params:c,state:e=>s(r.privateKey,{installationId:o.installationId,apiUrl:o.apiUrl,connection:e,sessionToken:a,nonce:l})});if(u instanceof Response)return u;let d=g(n);return b({url:u},200,{"set-cookie":`${d.name}=${l}; ${d.attributes}; Max-Age=${String(t)}`})}async function S(e,t){let n=new URL(e.url),r=g(e),i=await l(t.privateKey,n.searchParams.get(`state`),_(e,r.name));return i===null?y(f({ok:!1,message:`This connection link has expired or was opened in another browser; start again`})):y(f(await t.complete({state:i,url:n})),{"set-cookie":`${r.name}=; ${r.attributes}; Max-Age=0`})}exports.OAUTH_RESULT_MESSAGE=n,exports.OAUTH_START_MESSAGE=i,exports.OAUTH_START_PATH=a,exports.OAUTH_START_READY_MESSAGE=r,exports.finishAuthorization=S,exports.oauthCallbackPage=f,exports.oauthStartPage=p,exports.startAuthorization=x;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@picked/app-protocol"),t=require("@picked/admin-api-client");var n=/^app_[a-z0-9]+$/;function r(e){return n.test(e)}var i=/^[a-z0-9_-]{1,64}$/;function a(e){if(!r(e.appId))throw TypeError(`${e.appId} is not an app ID`);if(!i.test(e.namespace)||!i.test(e.key))throw TypeError(`${e.namespace}.${e.key} is not a label namespace and key`);let t=new URL(`/apps/${e.appId}/open`,new URL(e.dashboardOrigin).origin);return t.searchParams.set(`label`,`${e.namespace}.${e.key}`),t.searchParams.set(`value`,e.value),t.toString()}var o=6e4,s=class extends Error{userErrors;constructor(e){super(e.map(e=>`${e.field.join(`.`)}: ${e.message}`).join(`; `)),this.name=`LabelWriteError`,this.userErrors=e}},c={TEXT:`text`,NUMBER:`number`,BOOLEAN:`boolean`,JSON:`json`},l={text:`TEXT`,number:`NUMBER`,boolean:`BOOLEAN`,json:`JSON`};function u(e){return{...e,type:c[e.type]}}function d(e){if(e.length!==0)throw new s(e.map(e=>({field:e.field??[],message:e.message,code:e.code})))}function f(n){let r=(0,e.trimTrailing)(n.apiUrl,`/`),i=(0,t.createAdminApiClient)({apiUrl:r,...n.fetch===void 0?{}:{fetch:n.fetch}}),{installationId:a}=n,s=n.token===void 0||n.tokenExpiresAt===void 0?void 0:{token:n.token,expiresAt:Date.parse(n.tokenExpiresAt)},c=async()=>{if(s!==void 0&&s.expiresAt-o>Date.now())return s.token;let c=await(0,e.createAppAssertion)(await(0,e.signerFor)(n.privateKey),{installationId:a,audience:r}),{appInstallationTokenCreate:l}=await i.request(t.AppInstallationTokenCreateDocument,{input:{installationId:a,...n.sessionToken===void 0?{}:{sessionToken:n.sessionToken}}},{headers:{[e.APP_ASSERTION_HEADER]:c}});return s={token:l.token,expiresAt:Date.parse(l.expiresAt)},l.token},f=async(e,t,n={})=>i.request(e,t,{...n,headers:{...n.headers,"x-api-key":await c()}}),p={ownerType:`APP_INSTALLATION`,ownerId:a},m={async list(){return((await f(t.AppInstallationLabelsDocument,{id:a})).appInstallation?.labels??[]).map(u)},async get(e,t){return(await m.list()).find(n=>n.namespace===e&&n.key===t)},async set(e,n={}){let{labelsSet:r}=await f(t.LabelsSetDocument,{input:{...p,labels:e.map(e=>({...e,type:l[e.type]})),atomic:n.atomic??!1}});return d(r.userErrors),{labels:r.labels.map(u),skipped:r.skipped.map(u)}},async delete(e){let{labelsDelete:n}=await f(t.LabelsDeleteDocument,{input:{...p,labels:[...e]}});return d(n.userErrors),n.deletedLabelIds}};return{labels:m,token:c,request:f}}function p(e,t){return f({apiUrl:e.apiUrl,installationId:e.installationId,privateKey:t.privateKey,...e.token===void 0||e.tokenExpiresAt===void 0?{}:{token:e.token,tokenExpiresAt:e.tokenExpiresAt},...t.sessionToken==null?{}:{sessionToken:t.sessionToken},...t.fetch===void 0?{}:{fetch:t.fetch}})}function m(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"content-type":`application/json`}})}function h(e,t){let n=t.frameAncestors.length>0?t.frameAncestors.join(` `):`'none'`;return new Response(e,{headers:{"content-type":`text/html; charset=utf-8`,"content-security-policy":`frame-ancestors ${n}`,"referrer-policy":`strict-origin`,"x-content-type-options":`nosniff`}})}function g(e,t){return typeof e==`object`&&!!e&&`type`in e&&e.type===t}function _(e){return g(e,`workflow_tool.call`)}function v(e){return g(e,`installation.created`)}function y(e){return g(e,`installation.upgraded`)}function b(e){return g(e,`workspace.published`)}function x(e){return g(e,`installation.deleted`)}async function S(e,t){try{return m({output:await e(t)})}catch(e){return m({error:{message:e instanceof Error?e.message:String(e)}},422)}}function C(e,t,n,r){return r!==void 0&&t===r?{kind:`mcp`}:e===`POST`?n===`/lifecycle`?{kind:`lifecycle`}:n===`/events`?{kind:`event`}:n.startsWith(`/tools/`)?{kind:`tool`,name:n.slice(7)}:null:null}async function w(e,t,n){let r=n===``?null:JSON.parse(n);if(t.kind===`lifecycle`)return v(r)?await e.onInstall?.(r):y(r)?await e.onUpgrade?.(r):x(r)&&await e.onUninstall?.(r),m({ok:!0});if(t.kind===`event`)return b(r)&&await e.onEvent?.(r),m({ok:!0});let i=t.kind===`tool`?t.name:``,a=e.tools?.[i];return a===void 0||!_(r)?m({error:{message:`Unknown tool ${i}`}},404):S(a,r)}function T(t){let n=(0,e.trimTrailing)(t.basePath??``,`/`);return async r=>{let i=new URL(r.url).pathname,a=n!==``&&i.startsWith(n)?i.slice(n.length):i,o=C(r.method,i,a,t.mcp?.path);if(o===null&&t.manifest!==void 0&&a===`/manifest.json`)return m(t.manifest);if(o===null)return t.fallback===void 0?m({error:`not_found`},404):t.fallback(r);let s=r.method===`GET`?``:await r.text();if(o.kind!==`mcp`||t.mcp===void 0)return await(0,e.verifyBody)(t.pickedKeys,s,r.headers.get(e.SIGNATURE_HEADER))?w(t,o,s):m({error:`invalid_signature`},401);let c=await(0,e.verifyMcpRequest)(t.pickedKeys,r.headers,s);return c===null?m({error:`invalid_signature`},401):t.mcp.handle(new Request(r.url,{method:r.method,headers:r.headers,...r.method===`GET`?{}:{body:s}}),c)}}function E(e){let t=e.headers.get(`authorization`)??``;return t.startsWith(`Bearer `)&&t.length>7?t.slice(7):null}async function D(t,n){let r=E(t);return r===null?null:(0,e.verifySessionToken)(n,r)}function O(e,t){let n=e?.trim()??``;if(n===``)throw Error(t);return n}var k=`picked-api`;function A(e,t){if(typeof e!=`object`||!e)return;let n=Reflect.get(e,t);return typeof n==`string`&&n!==``?n:void 0}function j(e,t){let n;try{n=JSON.parse(e.VERCEL_RELATED_PROJECTS??`[]`)}catch{return}if(!Array.isArray(n))return;let r=n.find(e=>A(Reflect.get(Object(e),`project`),`name`)===t);if(r===void 0)return;let i=Reflect.get(Object(r),`preview`),a=Reflect.get(Object(r),`production`),o;return e.VERCEL_ENV===`preview`?o=A(i,`customEnvironment`)??A(i,`branch`):e.VERCEL_ENV===`production`&&(o=A(a,`alias`)??A(a,`url`)),o===void 0?void 0:`https://${o}`}function M(e){if(typeof e!=`object`||!e)return;let t=Reflect.get(e,`dashboardOrigins`);if(Array.isArray(t))return t.filter(e=>typeof e==`string`)}function N(e){let t=e.fetch??fetch,n=e.refreshAfterMs??3e5,r=e.timeoutMs??5e3,i=[],a,o,s=async()=>{let n=await t(e.url,{signal:AbortSignal.timeout(r)});if(!n.ok)return;let o=M(await n.json());o!==void 0&&(i=o,a=Date.now())};return async()=>(a!==void 0&&Date.now()-a<n||(o??=s().finally(()=>{o=void 0}),await o.catch(()=>void 0)),[...i])}function P(t,n,i={}){let a=O(t.PICKED_APP_PRIVATE_KEY,"Set PICKED_APP_PRIVATE_KEY to the app's private key, from `picked app keys generate`"),o=t.API_PORT===void 0?void 0:`http://localhost:${t.API_PORT}`,s=(0,e.trimTrailing)(O(t.PICKED_API_URL??j(t,k)??o,`Set PICKED_API_URL to the Picked API the app trusts`),`/`),c=(t.PICKED_DASHBOARD_ORIGINS??``).split(`,`).map(e=>e.trim()).filter(e=>e!==``),l=t.DASHBOARD_PORT,u=c;u.length===0&&l!==void 0&&(u=[`http://localhost:${l}`,`http://127.0.0.1:${l}`]);let d=t.VERCEL_ENV===`production`?t.VERCEL_PROJECT_PRODUCTION_URL:t.VERCEL_BRANCH_URL,f=d===void 0||d===``?void 0:`https://${d}`,p=t.PICKED_APP_ID?.trim()??``,m=i.fetch===void 0?{}:{fetch:i.fetch};return{privateKey:a,pickedUrl:s,pickedKeys:(0,e.remoteKeySet)({url:`${s}${e.PICKED_KEYS_PATH}`,...m}),publicUrl:(0,e.trimTrailing)(t.APP_PUBLIC_URL?.trim()||f||`http://localhost:${n}`,`/`),frameAncestors:[...u],dashboardOrigins:u.length>0?()=>Promise.resolve([...u]):N({url:`${s}${e.PICKED_CONFIG_PATH}`,...m,...i.refreshAfterMs===void 0?{}:{refreshAfterMs:i.refreshAfterMs},...i.timeoutMs===void 0?{}:{timeoutMs:i.timeoutMs}}),appId:r(p)?p:null}}function F(e){return async t=>{let n=new URL(t.url),r=n.searchParams.get(`__path`);if(r===null)return e(t);n.searchParams.delete(`__path`),n.pathname=r.startsWith(`/`)?r:`/${r}`;let i=t.method===`GET`||t.method===`HEAD`?null:await t.text();return e(new Request(n,{method:t.method,headers:t.headers,body:i}))}}exports.LabelWriteError=s,exports.createAppHandler=T,exports.createInstallationClient=f,exports.installationClientFor=p,exports.installationLinkUrl=a,exports.isAppId=r,exports.json=m,exports.page=h,exports.readAppEnvironment=P,exports.readSession=D,exports.remoteDashboardOrigins=N,exports.sessionTokenOf=E,exports.withRewrittenPath=F,Object.keys(e).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@picked/app-protocol");var t=/^[a-z0-9_-]{1,64}$/;function n(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function r(e){return e===`TEXT`?`text`:e===`NUMBER`?`number`:e===`BOOLEAN`?`boolean`:e===`JSON`?`json`:null}function i(e){if(!n(e))return null;let{namespace:t,key:i,value:a,increment:o,expiresIn:s,onlyIfAbsent:c,secret:l}=e,u=r(e.type);return typeof t!=`string`||typeof i!=`string`||u===null?null:{namespace:t,key:i,type:u,...typeof a==`string`?{value:a}:{},...typeof o==`number`?{increment:o}:{},...typeof s==`number`?{expiresIn:s}:{},...c===!0?{onlyIfAbsent:c}:{},...l===!0?{secret:l}:{}}}function a(e,t){let n=`${t.namespace}.${t.key}`,r=e.get(n);if(t.onlyIfAbsent===!0&&r!==void 0)return{label:r,skipped:r.value!==t.value};let i=t.increment===void 0?t.value??``:String(Number(r?.value??`0`)+t.increment),a={namespace:t.namespace,key:t.key,type:t.type,value:i,secret:t.secret===!0,expiresAt:t.expiresIn===void 0?null:new Date(Date.now()+t.expiresIn*1e3).toISOString(),updatedAt:new Date().toISOString()};return e.set(n,a),{label:a,skipped:!1}}function o(e){return{...e,type:e.type.toUpperCase()}}function s(e,t){return Response.json({data:null,errors:[{message:e,extensions:{code:t}}]})}function c(e,r,s){let c=Array.isArray(s.labels)?s.labels:[];if(r===`AppInstallationLabelsDelete`){let t=c.flatMap(e=>n(e)?[`${String(e.namespace)}.${String(e.key)}`]:[]).filter(t=>e.delete(t));return Response.json({data:{labelsDelete:{deletedLabelIds:t,userErrors:[]}}})}let l=c.map(i),u=l.findIndex(e=>e===null||!t.test(e.namespace)||!t.test(e.key));if(u!==-1){let e=[{field:[`labels`,String(u),`key`],message:`Key is invalid.`,code:`KEY_INVALID`}];return Response.json({data:{labelsSet:{labels:[],skipped:[],userErrors:e}}})}let d=l.flatMap(t=>t===null?[]:[a(e,t)]);return Response.json({data:{labelsSet:{labels:d.filter(e=>!e.skipped).map(e=>o(e.label)),skipped:d.filter(e=>e.skipped).map(e=>o(e.label)),userErrors:[]}}})}async function l(t){let r=t.apiUrl??`https://api.picked.test`,i=(0,e.staticKeySet)([await(0,e.createSigner)(t.appPrivateKey)]),a=await(0,e.createSigner)((await(0,e.generateKeyPair)()).privateKey),l=new Map,u=new Map,d=[],f=e=>{let t=`appt_fake_${String(u.size+1)}`;return u.set(t,e),Promise.resolve({token:t,expiresAt:new Date(Date.now()+36e5).toISOString()})},p=async(n,o)=>{let c=n.get(e.APP_ASSERTION_HEADER)??``,l=await(0,e.verifyAppAssertion)(i,c,{audience:r});if(l===null||l.installationId!==o.installationId)return s(`The app assertion does not verify`,`UNAUTHENTICATED`);let u=typeof o.sessionToken==`string`?await(0,e.verifySessionToken)((0,e.staticKeySet)([a]),o.sessionToken):null;return t.offlineAccess!==!0&&u?.installationId!==l.installationId?s(`The installation has no offline access; use the token Picked sends with each request`,`FORBIDDEN`):Response.json({data:{appInstallationTokenCreate:await f(l.installationId)}})},m=async(e,t)=>{let r=JSON.parse(t),i=n(r)&&typeof r.operationName==`string`?r.operationName:``,a=n(r)&&n(r.variables)?r.variables:{};d.push({operationName:i,variables:a});let f=n(a.input)?a.input:{};if(i===`AppInstallationTokenCreate`)return p(e,f);let m=u.get(e.get(`x-api-key`)??``);if(m===void 0)return s(`Authentication failed: Invalid API key`,`UNAUTHENTICATED`);let h=l.get(m)??new Map;if(l.set(m,h),i===`AppInstallationLabels`){let e=a.id===m;return Response.json({data:{appInstallation:e?{labels:[...h.values()].map(o)}:null}})}return f.ownerType!==`APP_INSTALLATION`||f.ownerId!==m?s(`Forbidden`,`FORBIDDEN`):c(h,i,f)};return{apiUrl:r,fetch:async(t,n)=>{let r=new URL(t instanceof Request?t.url:t);return r.pathname===e.PICKED_KEYS_PATH?Response.json((0,e.toJwks)([a])):r.pathname===`/api/graphql`?m(new Headers(n?.headers),typeof n?.body==`string`?n.body:`{}`):Response.json({error:{message:`Not found`}},{status:404})},pickedKeys:(0,e.staticKeySet)([a]),labels:l,received:d,tokenFor:f,async installationRef(e,t={}){let{token:n,expiresAt:i}=await f(e);return{installationId:e,organizationId:`org`,projectId:`proj_1`,apiUrl:r,...t,token:n,tokenExpiresAt:i}},async signedRequest(t,n){let r=JSON.stringify(n);return new Request(t,{method:`POST`,body:r,headers:{[e.SIGNATURE_HEADER]:await(0,e.signBody)(a,r)}})},async mcpRequest(t,n,r){let i=JSON.stringify(r);return new Request(t,{method:`POST`,body:i,headers:{"content-type":`application/json`,...await(0,e.signMcpRequest)(a,n,i)}})},async sessionRequest(t){let n=await(0,e.createSessionToken)(a,{...t.ref,userId:`user-test`,exp:Math.floor(Date.now()/1e3)+60});return new Request(t.url,{method:t.method??`GET`,headers:{authorization:`Bearer ${n}`,"content-type":`application/json`},...t.body===void 0?{}:{body:JSON.stringify(t.body)}})}}}function u(e,t,n,r=!1){return{type:`workflow_tool.call`,...e,tool:t,arguments:n,idempotencyKey:`wrun_test:${t}`,run:{runId:r?`test_wrun`:`wrun_test`,nodeId:`n1`,test:r}}}function d(e){return(e.headers.get(`set-cookie`)??``).split(`;`)[0]??``}exports.createFakePicked=l,exports.oauthCookie=d,exports.toolCall=u;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region src/frame.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return typeof e == "object" && !!e && "type" in e && e.type === "picked:context" && "context" in e;
|
|
4
|
+
}
|
|
5
|
+
function t(e) {
|
|
6
|
+
window.parent.postMessage(e, i());
|
|
7
|
+
}
|
|
8
|
+
function n() {
|
|
9
|
+
try {
|
|
10
|
+
return new URL(document.referrer).origin;
|
|
11
|
+
} catch {
|
|
12
|
+
return window.location.origin;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var r;
|
|
16
|
+
function i() {
|
|
17
|
+
return r ??= n(), r;
|
|
18
|
+
}
|
|
19
|
+
function a(n) {
|
|
20
|
+
if (window.parent === window) throw Error("connect() works only inside the Picked dashboard, which frames the page");
|
|
21
|
+
let r = (t) => {
|
|
22
|
+
t.source === window.parent && t.origin === i() && e(t.data) && (window.removeEventListener("message", r), n(t.data.context));
|
|
23
|
+
};
|
|
24
|
+
return window.addEventListener("message", r), t({ type: "picked:ready" }), () => window.removeEventListener("message", r);
|
|
25
|
+
}
|
|
26
|
+
function o(e = {}) {
|
|
27
|
+
return e.dashboardOrigin !== void 0 && (r = e.dashboardOrigin), new Promise((e) => {
|
|
28
|
+
a(e);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
var s = 24e4;
|
|
32
|
+
function c(e, t) {
|
|
33
|
+
let n = e.split(".")[1] ?? "", r;
|
|
34
|
+
try {
|
|
35
|
+
let e = atob(n.replaceAll("-", "+").replaceAll("_", "/"));
|
|
36
|
+
r = Reflect.get(Object(JSON.parse(e)), "exp");
|
|
37
|
+
} catch {
|
|
38
|
+
r = void 0;
|
|
39
|
+
}
|
|
40
|
+
let i = typeof r == "number" ? r * 1e3 - 12e4 - t : s;
|
|
41
|
+
return Math.min(s, Math.max(6e4, i));
|
|
42
|
+
}
|
|
43
|
+
function l() {
|
|
44
|
+
return new Promise((e) => {
|
|
45
|
+
let t = () => void 0, n = setTimeout(() => {
|
|
46
|
+
t(), e(void 0);
|
|
47
|
+
}, 1e4);
|
|
48
|
+
try {
|
|
49
|
+
t = a((t) => {
|
|
50
|
+
clearTimeout(n), e(t);
|
|
51
|
+
});
|
|
52
|
+
} catch {
|
|
53
|
+
clearTimeout(n), e(void 0);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async function u(e = {}) {
|
|
58
|
+
let t = await o(e), n = Date.now() + c(t.sessionToken, Date.now()), r = !1, i = !1, a = async () => {
|
|
59
|
+
if (r || i || Date.now() < n) return;
|
|
60
|
+
r = !0;
|
|
61
|
+
let e = await l();
|
|
62
|
+
r = !1, e === void 0 ? n = Date.now() + 15e3 : (t = e, n = Date.now() + c(e.sessionToken, Date.now()));
|
|
63
|
+
}, s = () => {
|
|
64
|
+
document.visibilityState === "visible" && a();
|
|
65
|
+
}, u = setInterval(() => void a(), 15e3);
|
|
66
|
+
return window.addEventListener("focus", s), document.addEventListener("visibilitychange", s), {
|
|
67
|
+
context: () => t,
|
|
68
|
+
stop: () => {
|
|
69
|
+
i = !0, clearInterval(u), window.removeEventListener("focus", s), document.removeEventListener("visibilitychange", s);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function d(e) {
|
|
74
|
+
t({
|
|
75
|
+
type: "picked:resize",
|
|
76
|
+
height: Math.ceil(e)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function f() {
|
|
80
|
+
let e = new ResizeObserver(() => d(document.documentElement.scrollHeight));
|
|
81
|
+
return e.observe(document.body), d(document.documentElement.scrollHeight), () => e.disconnect();
|
|
82
|
+
}
|
|
83
|
+
var p = {
|
|
84
|
+
intervalMs: 1e3,
|
|
85
|
+
attempts: 30
|
|
86
|
+
};
|
|
87
|
+
function m(e, t) {
|
|
88
|
+
return typeof e == "object" && !!e && "type" in e && e.type === t;
|
|
89
|
+
}
|
|
90
|
+
function h(e) {
|
|
91
|
+
return m(e, "picked-app:oauth-result") && "ok" in e && typeof e.ok == "boolean" && "message" in e && typeof e.message == "string";
|
|
92
|
+
}
|
|
93
|
+
function g(e) {
|
|
94
|
+
let t = window.open("/oauth/start", "picked-app-authorize", "width=520,height=720");
|
|
95
|
+
if (t === null) return Promise.resolve({
|
|
96
|
+
ok: !1,
|
|
97
|
+
message: "Allow pop-ups to connect"
|
|
98
|
+
});
|
|
99
|
+
let { origin: n } = window.location;
|
|
100
|
+
return new Promise((r) => {
|
|
101
|
+
let i = (e) => {
|
|
102
|
+
window.removeEventListener("message", c), window.removeEventListener("focus", u), window.clearInterval(d), r(e);
|
|
103
|
+
}, a = {
|
|
104
|
+
ok: !1,
|
|
105
|
+
message: "The connection window closed. If you finished connecting there, it shows here"
|
|
106
|
+
}, o = !1, s = (e) => {
|
|
107
|
+
o || (o = !0, i(e));
|
|
108
|
+
}, c = (r) => {
|
|
109
|
+
r.source === t && r.origin === n && (m(r.data, "picked-app:oauth-start-ready") ? t.postMessage({
|
|
110
|
+
type: "picked-app:oauth-start",
|
|
111
|
+
sessionToken: e.sessionToken,
|
|
112
|
+
params: e.params ?? null
|
|
113
|
+
}, n) : h(r.data) && s({
|
|
114
|
+
ok: r.data.ok,
|
|
115
|
+
message: r.data.message
|
|
116
|
+
}));
|
|
117
|
+
}, l = async (t) => {
|
|
118
|
+
let n = await e.connected?.().catch(() => null);
|
|
119
|
+
o || (typeof n == "string" ? s({
|
|
120
|
+
ok: !0,
|
|
121
|
+
message: n
|
|
122
|
+
}) : t + 1 >= p.attempts ? s(a) : window.setTimeout(() => void l(t + 1), p.intervalMs));
|
|
123
|
+
}, u = () => {
|
|
124
|
+
e.connected === void 0 ? s(a) : l(0);
|
|
125
|
+
}, d = window.setInterval(() => {
|
|
126
|
+
t.closed && (window.clearInterval(d), document.hasFocus() ? u() : window.addEventListener("focus", u, { once: !0 }));
|
|
127
|
+
}, 500);
|
|
128
|
+
window.addEventListener("message", c);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { g as authorizeInPopup, f as autoResize, o as connect, u as keepConnected, d as resize };
|
package/dist/esm/node.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile as e } from "node:fs/promises";
|
|
2
|
+
import { createServer as t } from "node:http";
|
|
3
|
+
//#region src/node.ts
|
|
4
|
+
async function n(e) {
|
|
5
|
+
let t = [];
|
|
6
|
+
for await (let n of e) t.push(Buffer.isBuffer(n) ? n : Buffer.from(String(n)));
|
|
7
|
+
return Buffer.concat(t);
|
|
8
|
+
}
|
|
9
|
+
async function r(e, t) {
|
|
10
|
+
let r = new Headers();
|
|
11
|
+
for (let [t, n] of Object.entries(e.headers)) typeof n == "string" ? r.set(t, n) : Array.isArray(n) && r.set(t, n.join(", "));
|
|
12
|
+
let i = e.method ?? "GET", a = i === "GET" || i === "HEAD" ? null : new Uint8Array(await n(e));
|
|
13
|
+
return new Request(`http://localhost:${t}${e.url ?? "/"}`, {
|
|
14
|
+
method: i,
|
|
15
|
+
headers: r,
|
|
16
|
+
body: a
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function i(e, t) {
|
|
20
|
+
t.statusCode = e.status, e.headers.forEach((e, n) => t.setHeader(n, e)), t.end(Buffer.from(await e.arrayBuffer()));
|
|
21
|
+
}
|
|
22
|
+
function a(e, n) {
|
|
23
|
+
let a = t((t, a) => {
|
|
24
|
+
(async () => {
|
|
25
|
+
try {
|
|
26
|
+
let o = await e(await r(t, n));
|
|
27
|
+
console.log(`${t.method ?? ""} ${t.url ?? ""} ${o.status}`), await i(o, a);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(e), a.statusCode = 500, a.end("Internal error");
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
});
|
|
33
|
+
return a.listen(n, () => console.log(`Listening on http://localhost:${n}`)), a;
|
|
34
|
+
}
|
|
35
|
+
async function o() {
|
|
36
|
+
let t = await e(new URL("../esm/frame.js", "" + import.meta.url), "utf8");
|
|
37
|
+
return new Response(t, { headers: { "content-type": "text/javascript" } });
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { o as frameScript, a as serve };
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { deriveSecretKey as e, fromBase64url as t, toBase64url as n, verifySessionToken as r } from "@picked/app-protocol";
|
|
2
|
+
//#region src/oauth.ts
|
|
3
|
+
var i = 600, a = "picked-app:oauth-result", o = "picked-app:oauth-start-ready", s = "picked-app:oauth-start", c = "/oauth/start", l = 12;
|
|
4
|
+
async function u(t, r) {
|
|
5
|
+
let a = {
|
|
6
|
+
...r,
|
|
7
|
+
exp: Math.floor(Date.now() / 1e3) + i
|
|
8
|
+
}, o = globalThis.crypto.getRandomValues(new Uint8Array(l)), s = await globalThis.crypto.subtle.encrypt({
|
|
9
|
+
name: "AES-GCM",
|
|
10
|
+
iv: o
|
|
11
|
+
}, await e(t, "oauth-state"), new TextEncoder().encode(JSON.stringify(a))), c = new Uint8Array(l + s.byteLength);
|
|
12
|
+
return c.set(o), c.set(new Uint8Array(s), l), n(c);
|
|
13
|
+
}
|
|
14
|
+
async function d(n, r) {
|
|
15
|
+
try {
|
|
16
|
+
let i = t(r), a = await globalThis.crypto.subtle.decrypt({
|
|
17
|
+
name: "AES-GCM",
|
|
18
|
+
iv: i.slice(0, l)
|
|
19
|
+
}, await e(n, "oauth-state"), i.slice(l));
|
|
20
|
+
return JSON.parse(new TextDecoder().decode(a));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function f(e, t, n) {
|
|
26
|
+
if (t === null || t === "") return null;
|
|
27
|
+
let r = await d(e, t);
|
|
28
|
+
if (typeof r != "object" || !r) return null;
|
|
29
|
+
let i = (e) => Reflect.get(r, e), [a, o, s, c, l, u] = [
|
|
30
|
+
i("installationId"),
|
|
31
|
+
i("apiUrl"),
|
|
32
|
+
i("connection"),
|
|
33
|
+
i("exp"),
|
|
34
|
+
i("nonce"),
|
|
35
|
+
i("sessionToken")
|
|
36
|
+
];
|
|
37
|
+
return typeof a != "string" || typeof o != "string" || typeof s != "string" || typeof c != "number" || typeof l != "string" || l !== n || typeof u != "string" || c < Date.now() / 1e3 ? null : {
|
|
38
|
+
installationId: a,
|
|
39
|
+
apiUrl: o,
|
|
40
|
+
connection: s,
|
|
41
|
+
exp: c,
|
|
42
|
+
nonce: l,
|
|
43
|
+
sessionToken: u
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function p(e) {
|
|
47
|
+
return e.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
48
|
+
}
|
|
49
|
+
function m(e) {
|
|
50
|
+
return JSON.stringify(e).replaceAll("<", String.raw`\u003c`);
|
|
51
|
+
}
|
|
52
|
+
function h(e) {
|
|
53
|
+
let t = m({
|
|
54
|
+
type: a,
|
|
55
|
+
...e
|
|
56
|
+
});
|
|
57
|
+
return `<!doctype html>
|
|
58
|
+
<html lang="en">
|
|
59
|
+
<head><meta charset="utf-8" /><title>${p(e.message)}</title></head>
|
|
60
|
+
<body style="font: 14px system-ui, sans-serif; padding: 24px">
|
|
61
|
+
<p>${p(e.message)}</p>
|
|
62
|
+
<p id="close" hidden>You can close this window and return to Picked.</p>
|
|
63
|
+
<script>
|
|
64
|
+
if (window.opener) {
|
|
65
|
+
window.opener.postMessage(${t}, window.location.origin)
|
|
66
|
+
window.close()
|
|
67
|
+
} else {
|
|
68
|
+
document.getElementById('close').hidden = false
|
|
69
|
+
}
|
|
70
|
+
<\/script>
|
|
71
|
+
</body>
|
|
72
|
+
</html>`;
|
|
73
|
+
}
|
|
74
|
+
function g() {
|
|
75
|
+
return `<!doctype html>
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<head><meta charset="utf-8" /><title>Connecting…</title></head>
|
|
78
|
+
<body style="font: 14px system-ui, sans-serif; padding: 24px">
|
|
79
|
+
<p id="status">Connecting…</p>
|
|
80
|
+
<script>
|
|
81
|
+
const status = document.getElementById('status')
|
|
82
|
+
const fail = (message) => {
|
|
83
|
+
status.textContent = message
|
|
84
|
+
if (!window.opener) return
|
|
85
|
+
window.opener.postMessage({ type: ${m(a)}, ok: false, message }, window.location.origin)
|
|
86
|
+
window.close()
|
|
87
|
+
}
|
|
88
|
+
let started = false
|
|
89
|
+
window.addEventListener('message', async (event) => {
|
|
90
|
+
if (event.origin !== window.location.origin || event.source !== window.opener) return
|
|
91
|
+
const data = event.data
|
|
92
|
+
if (started || !data || data.type !== ${m(s)} || typeof data.sessionToken !== 'string') return
|
|
93
|
+
started = true
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch(window.location.pathname, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: { authorization: 'Bearer ' + data.sessionToken, 'content-type': 'application/json' },
|
|
98
|
+
body: JSON.stringify(data.params ?? null),
|
|
99
|
+
})
|
|
100
|
+
const answer = await response.json()
|
|
101
|
+
const url = typeof answer.url === 'string' ? new URL(answer.url) : null
|
|
102
|
+
if (!response.ok || url === null || (url.protocol !== 'https:' && url.protocol !== 'http:')) {
|
|
103
|
+
fail(answer.error?.message ?? 'The app answered ' + response.status)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
window.location.replace(url.href)
|
|
107
|
+
} catch {
|
|
108
|
+
fail('The app could not start connecting')
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
if (window.opener) window.opener.postMessage({ type: ${m(o)} }, window.location.origin)
|
|
112
|
+
else status.textContent = 'Start connecting from the app’s page in Picked.'
|
|
113
|
+
<\/script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>`;
|
|
116
|
+
}
|
|
117
|
+
var _ = /* @__PURE__ */ new Set([
|
|
118
|
+
"localhost",
|
|
119
|
+
"127.0.0.1",
|
|
120
|
+
"[::1]"
|
|
121
|
+
]);
|
|
122
|
+
function v(e) {
|
|
123
|
+
let t = new URL(e.url), n = e.headers.get("host") ?? t.host, r = e.headers.get("x-forwarded-proto")?.split(",")[0]?.trim(), i = r === "https" || r === "http" ? r : t.protocol.slice(0, -1);
|
|
124
|
+
try {
|
|
125
|
+
return new URL(`${i}://${n}`);
|
|
126
|
+
} catch {
|
|
127
|
+
return t;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function y(e) {
|
|
131
|
+
let t = v(e);
|
|
132
|
+
return t.protocol === "http:" && _.has(t.hostname) ? {
|
|
133
|
+
name: `picked-oauth-${t.port || "80"}`,
|
|
134
|
+
attributes: "Path=/; HttpOnly; SameSite=Lax"
|
|
135
|
+
} : {
|
|
136
|
+
name: "__Host-picked-oauth",
|
|
137
|
+
attributes: "Path=/; HttpOnly; SameSite=Lax; Secure"
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function b(e, t) {
|
|
141
|
+
for (let n of (e.headers.get("cookie") ?? "").split(";")) {
|
|
142
|
+
let e = n.indexOf("=");
|
|
143
|
+
if (e !== -1 && n.slice(0, e).trim() === t) return n.slice(e + 1).trim();
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
var x = {
|
|
148
|
+
"cache-control": "no-store",
|
|
149
|
+
"x-content-type-options": "nosniff"
|
|
150
|
+
};
|
|
151
|
+
function S(e, t = {}) {
|
|
152
|
+
return new Response(e, { headers: {
|
|
153
|
+
...x,
|
|
154
|
+
"content-type": "text/html; charset=utf-8",
|
|
155
|
+
"content-security-policy": "frame-ancestors 'none'",
|
|
156
|
+
"referrer-policy": "no-referrer",
|
|
157
|
+
...t
|
|
158
|
+
} });
|
|
159
|
+
}
|
|
160
|
+
function C(e, t, n = {}) {
|
|
161
|
+
return new Response(JSON.stringify(e), {
|
|
162
|
+
status: t,
|
|
163
|
+
headers: {
|
|
164
|
+
...x,
|
|
165
|
+
"content-type": "application/json",
|
|
166
|
+
...n
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function w(e, t) {
|
|
171
|
+
if (e.method === "GET") return S(g());
|
|
172
|
+
if (e.method !== "POST") return C({ error: { message: "Method not allowed" } }, 405, { allow: "GET, POST" });
|
|
173
|
+
let n = e.headers.get("authorization") ?? "", a = n.startsWith("Bearer ") ? n.slice(7) : "", o = a === "" ? null : await r(t.pickedKeys, a);
|
|
174
|
+
if (o === null) return C({ error: { message: "Sign in again" } }, 401);
|
|
175
|
+
let s = await e.json().catch(() => null), c = globalThis.crypto.randomUUID(), l = await t.authorize({
|
|
176
|
+
session: o,
|
|
177
|
+
sessionToken: a,
|
|
178
|
+
params: s,
|
|
179
|
+
state: (e) => u(t.privateKey, {
|
|
180
|
+
installationId: o.installationId,
|
|
181
|
+
apiUrl: o.apiUrl,
|
|
182
|
+
connection: e,
|
|
183
|
+
sessionToken: a,
|
|
184
|
+
nonce: c
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
if (l instanceof Response) return l;
|
|
188
|
+
let d = y(e);
|
|
189
|
+
return C({ url: l }, 200, { "set-cookie": `${d.name}=${c}; ${d.attributes}; Max-Age=${String(i)}` });
|
|
190
|
+
}
|
|
191
|
+
async function T(e, t) {
|
|
192
|
+
let n = new URL(e.url), r = y(e), i = await f(t.privateKey, n.searchParams.get("state"), b(e, r.name));
|
|
193
|
+
return i === null ? S(h({
|
|
194
|
+
ok: !1,
|
|
195
|
+
message: "This connection link has expired or was opened in another browser; start again"
|
|
196
|
+
})) : S(h(await t.complete({
|
|
197
|
+
state: i,
|
|
198
|
+
url: n
|
|
199
|
+
})), { "set-cookie": `${r.name}=; ${r.attributes}; Max-Age=0` });
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { a as OAUTH_RESULT_MESSAGE, s as OAUTH_START_MESSAGE, c as OAUTH_START_PATH, o as OAUTH_START_READY_MESSAGE, T as finishAuthorization, h as oauthCallbackPage, g as oauthStartPage, w as startAuthorization };
|