kloudy 0.4.6 → 0.4.8
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/README.md +7 -0
- package/package.json +68 -68
- package/src/chooser-link.mjs +17 -0
- package/src/remote.mjs +26 -3
- package/src/website-return.mjs +18 -0
package/README.md
CHANGED
|
@@ -307,3 +307,10 @@ MCP: operation `conversation`, `conversation_operation` = `read`, `append` or `s
|
|
|
307
307
|
The engine owns encryption, scope checks, condensation gates and transcript deletion. Returned cards are lossy context, never grants. Artifact pointers do not authorize repository reads. The client never persists message bodies in its session file. Replayed commands return metadata only: issue a read for current cards. Conflicts and failures are not retried automatically; read the cursor and reconcile before resubmitting. Added in 0.4.1. The no-key installer and public discovery remain unchanged.
|
|
308
308
|
|
|
309
309
|
Official MCP Registry name: `ai.kloudy/kloudy`. Remote endpoint: https://kloudy.ai/mcp. Public discovery and Sample cards do not grant private tools or authorize spending.
|
|
310
|
+
|
|
311
|
+
## Card and website fallback (0.4.7)
|
|
312
|
+
|
|
313
|
+
The remote MCP adapter returns supported card metadata and a plain-text HTTPS chooser link together. Hosts control rendering and link launch. Call show_pending_chooser_link to display the last offer/selection again in the same MCP process. Website sign-in verifies private account projects; the public template locator is never an account token or authorization. A presentation goal may be retained in the current MCP process and is kept out of the URL. Automatic authenticated IDE return is not claimed. If the process resets, use the website's existing project recovery path. No models, installation or spending are activated by these links.
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
Website return attachment (0.4.8): show_visual_setup_choices retains the visible HTTPS chooser link and supported card metadata. With a goal, it also offers a waiting-connection link and matching code. On the website, use the existing verified account, 2FA and authorized project; confirm the matching waiting IDE. receive_website_selection then receives the exact encrypted inert manifest on that same MCP connection. No extension, website cookie in the IDE, copied account credential, new engine grant, installation, execution or spending is required. The public locator is not authorization. Delivery keys stay in this process; restarting it requires a fresh pairing, while the website restores the existing project's saved state. An unread receipt cannot be overwritten by a second selection. Hosts control native card display; no universal native-chat rendering claim is made.
|
package/package.json
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "kloudy",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Ask for Kloudy: CLI, MCP and SDK clients for the same scoped engine session.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "Apache-2.0",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=20.19.0"
|
|
9
|
-
},
|
|
10
|
-
"bin": {
|
|
11
|
-
"kloudy": "bin/entry.mjs"
|
|
12
|
-
},
|
|
13
|
-
"exports": {
|
|
14
|
-
".": "./src/client.mjs",
|
|
15
|
-
"./mcp": "./src/mcp.mjs",
|
|
16
|
-
"./wrap": "./src/wrap.mjs",
|
|
17
|
-
"./session": "./src/connection.mjs",
|
|
18
|
-
"./httpk": "./src/httpk.mjs",
|
|
19
|
-
"./hosted": "./src/hosted.mjs",
|
|
20
|
-
"./public": "./src/public.mjs",
|
|
21
|
-
"./strings": "./src/strings.mjs",
|
|
22
|
-
"./sdf": "./src/sdf.mjs",
|
|
23
|
-
"./query": "./src/query.mjs",
|
|
24
|
-
"./activity": "./src/activity.mjs",
|
|
25
|
-
"./status-indicator.css": "./src/status-indicator.css",
|
|
26
|
-
"./models": "./src/models.mjs",
|
|
27
|
-
"./host-interface": "./src/host-interface.mjs",
|
|
28
|
-
"./replies": "./src/replies.mjs",
|
|
29
|
-
"./conversation": "./src/conversation.mjs"
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"bin",
|
|
33
|
-
"src",
|
|
34
|
-
"schemas",
|
|
35
|
-
"README.md",
|
|
36
|
-
"MCP-DEFAULT.md",
|
|
37
|
-
"LICENSE"
|
|
38
|
-
],
|
|
39
|
-
"scripts": {
|
|
40
|
-
"test": "node --test test/*.test.mjs",
|
|
41
|
-
"check": "node --check bin/kloudy.mjs && node --check src/client.mjs && npm test",
|
|
42
|
-
"start": "node bin/kloudy.mjs",
|
|
43
|
-
"proof": "node scripts/prove.mjs",
|
|
44
|
-
"build:approval": "node scripts/build-approval.mjs"
|
|
45
|
-
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@modelcontextprotocol/ext-apps": "2.0.3",
|
|
48
|
-
"@modelcontextprotocol/sdk": "1.31.0",
|
|
49
|
-
"ajv": "8.20.0",
|
|
50
|
-
"ajv-formats": "3.0.1",
|
|
51
|
-
"js-tiktoken": "1.0.21",
|
|
52
|
-
"jsonc-parser": "3.3.1",
|
|
53
|
-
"smol-toml": "1.9.0",
|
|
54
|
-
"zod": "4.6.5"
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"esbuild": "0.28.2"
|
|
58
|
-
},
|
|
59
|
-
"mcpName": "ai.kloudy/kloudy",
|
|
60
|
-
"homepage": "https://kloudy.ai/install",
|
|
61
|
-
"publishConfig": {
|
|
62
|
-
"access": "public",
|
|
63
|
-
"registry": "https://registry.npmjs.org"
|
|
64
|
-
},
|
|
65
|
-
"bugs": {
|
|
66
|
-
"url": "https://kloudy.ai/support"
|
|
67
|
-
}
|
|
68
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "kloudy",
|
|
3
|
+
"version": "0.4.8",
|
|
4
|
+
"description": "Ask for Kloudy: CLI, MCP and SDK clients for the same scoped engine session.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.19.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"kloudy": "bin/entry.mjs"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/client.mjs",
|
|
15
|
+
"./mcp": "./src/mcp.mjs",
|
|
16
|
+
"./wrap": "./src/wrap.mjs",
|
|
17
|
+
"./session": "./src/connection.mjs",
|
|
18
|
+
"./httpk": "./src/httpk.mjs",
|
|
19
|
+
"./hosted": "./src/hosted.mjs",
|
|
20
|
+
"./public": "./src/public.mjs",
|
|
21
|
+
"./strings": "./src/strings.mjs",
|
|
22
|
+
"./sdf": "./src/sdf.mjs",
|
|
23
|
+
"./query": "./src/query.mjs",
|
|
24
|
+
"./activity": "./src/activity.mjs",
|
|
25
|
+
"./status-indicator.css": "./src/status-indicator.css",
|
|
26
|
+
"./models": "./src/models.mjs",
|
|
27
|
+
"./host-interface": "./src/host-interface.mjs",
|
|
28
|
+
"./replies": "./src/replies.mjs",
|
|
29
|
+
"./conversation": "./src/conversation.mjs"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin",
|
|
33
|
+
"src",
|
|
34
|
+
"schemas",
|
|
35
|
+
"README.md",
|
|
36
|
+
"MCP-DEFAULT.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "node --test test/*.test.mjs",
|
|
41
|
+
"check": "node --check bin/kloudy.mjs && node --check src/client.mjs && npm test",
|
|
42
|
+
"start": "node bin/kloudy.mjs",
|
|
43
|
+
"proof": "node scripts/prove.mjs",
|
|
44
|
+
"build:approval": "node scripts/build-approval.mjs"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/ext-apps": "2.0.3",
|
|
48
|
+
"@modelcontextprotocol/sdk": "1.31.0",
|
|
49
|
+
"ajv": "8.20.0",
|
|
50
|
+
"ajv-formats": "3.0.1",
|
|
51
|
+
"js-tiktoken": "1.0.21",
|
|
52
|
+
"jsonc-parser": "3.3.1",
|
|
53
|
+
"smol-toml": "1.9.0",
|
|
54
|
+
"zod": "4.6.5"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"esbuild": "0.28.2"
|
|
58
|
+
},
|
|
59
|
+
"mcpName": "ai.kloudy/kloudy",
|
|
60
|
+
"homepage": "https://kloudy.ai/install",
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public",
|
|
63
|
+
"registry": "https://registry.npmjs.org"
|
|
64
|
+
},
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://kloudy.ai/support"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Public presentation adapter. URLs contain public template locators only, never
|
|
2
|
+
// a connection credential, authorization token, goal text or identity claim.
|
|
3
|
+
export const CHOOSER_URL='https://kloudy.ai/api/customer/visual-chooser/start';
|
|
4
|
+
export const pendingChooserTool={name:'show_pending_chooser_link',description:'Show the pending Kloudy card and its HTTPS website fallback again. The website verifies account sign-in; the link is not authorization. This does not run a tool, model, installation or payment.',inputSchema:{type:'object',properties:{},additionalProperties:false},annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false}};
|
|
5
|
+
export function chooserResponse(result,{apps=false,goal}={}){
|
|
6
|
+
if(!result||typeof result!=='object'||Array.isArray(result)||result.isError)return result;
|
|
7
|
+
const data=result.structuredContent;if(!data||typeof data!=='object'||Array.isArray(data))return result;
|
|
8
|
+
const option=typeof data.selectedOptionId==='string'&&/^[a-z0-9-]{1,80}$/.test(data.selectedOptionId)?data.selectedOptionId:null;
|
|
9
|
+
const set=typeof data.choiceSetId==='string'&&/^visual-[a-f0-9]{1,64}$/.test(data.choiceSetId)?data.choiceSetId:null;
|
|
10
|
+
const url=option&&set?CHOOSER_URL+'#choice='+encodeURIComponent(option)+'&set='+encodeURIComponent(set):CHOOSER_URL;
|
|
11
|
+
const chooser={url,linkIsAuthorization:false,identityFromEmail:false,accountBindingVerified:false,returnMethod:'existing-authenticated-website-project-manifest',automaticIdeReturnVerified:false,...(set?{publicChoiceSetId:set}:{}),...(option?{publicOptionId:option}:{}),...(goal?{request:goal}:{})};
|
|
12
|
+
const text='Website chooser: '+url+'\nOpen this HTTPS link if your host does not show the card. Complete account sign-in/2FA on the website before opening private project choices. Email is a lookup hint, not proof. The host controls card rendering, clickable links and browser launch. Account-bound automatic return is not established by this public template locator.';
|
|
13
|
+
return {...result,content:[...(Array.isArray(result.content)?result.content:[]),{type:'text',text}],structuredContent:{...data,chooser},...(apps?{_meta:{...result._meta,ui:{...result._meta?.ui,resourceUri:'ui://widget/kloudy-visual-setup-v1.html'}}}:{})};
|
|
14
|
+
}
|
|
15
|
+
export function pendingChooserResponse(pending){
|
|
16
|
+
return pending?structuredClone(pending):{content:[{type:'text',text:'No pending chooser is retained in this MCP process. Open '+CHOOSER_URL+' and sign in to recover your existing project. This link is not a bearer token or authorization.'}],structuredContent:{pending:false,chooser:{url:CHOOSER_URL,linkIsAuthorization:false,accountBindingVerified:false}}};
|
|
17
|
+
}
|
package/src/remote.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
|
7
7
|
import {ListToolsRequestSchema,CallToolRequestSchema,ListResourcesRequestSchema,ReadResourceRequestSchema,ListResourceTemplatesRequestSchema,ToolListChangedNotificationSchema,ResourceListChangedNotificationSchema} from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
import {REMOTE} from './editor-install.mjs';
|
|
9
9
|
import {autoupdateEnabled} from './autoupdate.mjs';
|
|
10
|
+
import {chooserResponse,pendingChooserResponse,pendingChooserTool} from './chooser-link.mjs';
|
|
11
|
+
import {WebsiteSelectionReturn,websiteReturnTool} from './website-return.mjs';
|
|
10
12
|
const transports=new WeakMap();
|
|
11
13
|
// This proxy can relay Apps rendering, but cannot service sampling, roots or elicitation.
|
|
12
14
|
export function forwardedCapabilities(value={}){
|
|
@@ -28,7 +30,8 @@ export async function askRemote(question,options){
|
|
|
28
30
|
}
|
|
29
31
|
export async function serveRemote(options={}){
|
|
30
32
|
const connect=options.connect??connectRemote,enabled=autoupdateEnabled(options.autoupdate??process.env.KLOUDY_MCP_AUTOUPDATE);
|
|
31
|
-
let remote=await connect({...options,capabilities:{}}),ready=Promise.resolve(),closed=false,reconnecting;
|
|
33
|
+
let remote=await connect({...options,capabilities:{}}),ready=Promise.resolve(),closed=false,reconnecting,pendingChooser,requestGoal;
|
|
34
|
+
const attachment=options.websiteReturn??(options.connect?null:new WebsiteSelectionReturn());
|
|
32
35
|
const upstream=remote.getServerCapabilities(),capabilities={};
|
|
33
36
|
if(upstream.tools)capabilities.tools=enabled&&upstream.tools.listChanged?{listChanged:true}:{};
|
|
34
37
|
if(upstream.resources)capabilities.resources=enabled&&upstream.resources.listChanged?{listChanged:true}:{};
|
|
@@ -54,13 +57,33 @@ export async function serveRemote(options={}){
|
|
|
54
57
|
return run(remote);
|
|
55
58
|
}
|
|
56
59
|
};
|
|
57
|
-
if(capabilities.tools){
|
|
60
|
+
if(capabilities.tools){
|
|
61
|
+
server.setRequestHandler(ListToolsRequestSchema,async req=>{
|
|
62
|
+
const result=await invoke('listTools',req.params);
|
|
63
|
+
return {...result,tools:[...result.tools.map(tool=>tool.name==='show_visual_setup_choices'?{...tool,inputSchema:{...tool.inputSchema,properties:{...tool.inputSchema.properties,goal:{type:'string',minLength:1,maxLength:1000,description:'Presentation request retained in this MCP process; never sent as a credential or URL.'}}}}:tool),...(result.tools.some(t=>t.name===pendingChooserTool.name)?[]:[pendingChooserTool]),websiteReturnTool]};
|
|
64
|
+
});
|
|
65
|
+
server.setRequestHandler(CallToolRequestSchema,async req=>{
|
|
66
|
+
if(req.params.name===websiteReturnTool.name){const args=req.params.arguments||{};if(Object.keys(args).some(k=>k!=='waitSeconds'))throw Error('Return lookup accepts a bounded wait only');const result=attachment?await attachment.receive(args):{state:'unavailable'};if(result.state?.startsWith('received')&&pendingChooser)pendingChooser={...pendingChooser,structuredContent:{...pendingChooser.structuredContent,authenticatedReturn:result}};return {content:[{type:'text',text:JSON.stringify(result)}],structuredContent:result};}
|
|
67
|
+
if(req.params.name===pendingChooserTool.name){if(Object.keys(req.params.arguments||{}).length)throw Error('Pending chooser lookup takes no identity or credential fields');return pendingChooserResponse(pendingChooser);}
|
|
68
|
+
if(req.params.name!=='show_visual_setup_choices')return invoke('callTool',req.params);
|
|
69
|
+
const argumentsValue={...(req.params.arguments||{})};
|
|
70
|
+
let nextGoal=requestGoal;
|
|
71
|
+
if(Object.hasOwn(argumentsValue,'goal')){if(typeof argumentsValue.goal!=='string'||!argumentsValue.goal.trim()||argumentsValue.goal.length>1000)throw Error('Use a presentation request of 1-1000 characters');nextGoal=argumentsValue.goal;delete argumentsValue.goal;}
|
|
72
|
+
const result=await invoke('callTool',{...req.params,arguments:argumentsValue});
|
|
73
|
+
const decorated=chooserResponse(result,{apps:!!forwardedCapabilities(server.getClientCapabilities()).experimental,goal:nextGoal});
|
|
74
|
+
if(!decorated.isError&&nextGoal&&attachment){
|
|
75
|
+
try{const link=await attachment.open(nextGoal),priorUrl=decorated.structuredContent.chooser.url;decorated.content=decorated.content.map(c=>c.type==='text'&&c.text.startsWith('Website chooser: ')?{...c,text:c.text.replace(priorUrl,link.chooserUrl)}:c);decorated.structuredContent.chooser={...decorated.structuredContent.chooser,url:link.chooserUrl,returnMethod:'paired-encrypted-website-selection'};decorated.content.push({type:'text',text:'Open '+link.chooserUrl+' in your signed-in account. Confirm the waiting IDE code '+link.pairCode+' and the existing project. Then return the selection; receive_website_selection waits on this same MCP connection. The locator grants no account authority.'});decorated.structuredContent.websiteReturn={...link,extensionRequired:false,engineAuthority:false};}
|
|
76
|
+
catch{decorated.structuredContent.websiteReturn={available:false,manualManifestFallback:true};}
|
|
77
|
+
}
|
|
78
|
+
if(!decorated.isError){requestGoal=nextGoal;pendingChooser=decorated;}return decorated;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
58
81
|
// Resource delivery is independent of notification/autoupdate opt-in.
|
|
59
82
|
if(capabilities.resources){
|
|
60
83
|
server.setRequestHandler(ListResourcesRequestSchema,req=>invoke('listResources',req.params));
|
|
61
84
|
server.setRequestHandler(ReadResourceRequestSchema,req=>invoke('readResource',req.params));
|
|
62
85
|
server.setRequestHandler(ListResourceTemplatesRequestSchema,req=>invoke('listResourceTemplates',req.params));
|
|
63
86
|
}
|
|
64
|
-
server.onclose=()=>{closed=true;void ready.catch(()=>{}).then(()=>remote.close());};
|
|
87
|
+
server.onclose=()=>{closed=true;attachment?.close?.();void ready.catch(()=>{}).then(()=>remote.close());};
|
|
65
88
|
await server.connect(options.transport??new StdioServerTransport());return server;
|
|
66
89
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Delivery keys protect inert presentation data. They never authorize engine calls.
|
|
2
|
+
import {generateKeyPairSync,createPublicKey,diffieHellman,hkdfSync,createDecipheriv,createHash} from 'node:crypto';
|
|
3
|
+
const version='kloudy.website-return/1',hash=s=>createHash('sha256').update(s).digest('hex'),origin='https://kloudy.ai',root=origin+'/api/customer/visual-chooser/return/';
|
|
4
|
+
export const websiteReturnTool={name:'receive_website_selection',description:'Wait for the exact inert selection returned by the signed-in website to this MCP connection. No IDE extension, website cookie, account token, model or grant is needed. Use after showing the chooser link. A waiting result is safe to check again; do not repeat mutations after uncertain outcomes.',inputSchema:{type:'object',additionalProperties:false,properties:{waitSeconds:{type:'integer',minimum:0,maximum:45}}},annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false}};
|
|
5
|
+
export class WebsiteSelectionReturn{
|
|
6
|
+
constructor({fetcher=fetch,clock=()=>Date.now(),sleep=ms=>new Promise(r=>setTimeout(r,ms))}={}){Object.assign(this,{fetcher,clock,sleep});this.keys=generateKeyPairSync('x25519');this.publicKey=this.keys.publicKey.export({format:'jwk'}).x;this.sequence=0;this.receipt=null;}
|
|
7
|
+
async request(action,input){const response=await this.fetcher(root+action,{method:'POST',headers:{origin,'content-type':'application/json'},body:JSON.stringify(input),redirect:'error',signal:AbortSignal.timeout(10000)});const text=await response.text();if(text.length>300000)throw Error('Return response exceeds the bounded manifest');const value=JSON.parse(text);if(!response.ok)throw Error(typeof value.error==='string'?value.error:'Website return unavailable');return value;}
|
|
8
|
+
async open(goal){if(this.link)return this.link;if(typeof goal!=='string'||!goal.trim()||goal.length>1000)throw Error('Use a presentation goal up to 1000 characters');const result=await this.request('open',{publicKey:this.publicKey,goal});if(result.version!==version||!/^[a-f0-9]{32}$/.test(result.id)||result.linkIsAuthorization!==false||result.chooserUrl!==origin+'/api/customer/visual-chooser/start#return='+result.id||result.pairCode!==hash(result.id+':'+this.publicKey).slice(0,8).toUpperCase())throw Error('Unexpected website return attachment');this.link=result;return result;}
|
|
9
|
+
decrypt(envelope){
|
|
10
|
+
if(envelope?.version!==version||envelope.id!==this.link?.id||envelope.sequence!==this.sequence+1||!/^[A-Za-z0-9_-]{43}$/.test(envelope.serverPublicKey)||!/^[A-Za-z0-9_-]{16}$/.test(envelope.iv)||!/^[A-Za-z0-9_-]{22}$/.test(envelope.tag)||typeof envelope.ciphertext!=='string'||envelope.ciphertext.length>270000)throw Error('Unexpected return envelope');
|
|
11
|
+
const aad=JSON.stringify([version,this.link.id,hash(this.publicKey),envelope.sequence]),peer=createPublicKey({key:{kty:'OKP',crv:'X25519',x:envelope.serverPublicKey},format:'jwk'}),secret=diffieHellman({privateKey:this.keys.privateKey,publicKey:peer}),key=Buffer.from(hkdfSync('sha256',secret,Buffer.alloc(0),Buffer.from(aad),32)),decipher=createDecipheriv('aes-256-gcm',key,Buffer.from(envelope.iv,'base64url'));decipher.setAAD(Buffer.from(aad));decipher.setAuthTag(Buffer.from(envelope.tag,'base64url'));const payload=JSON.parse(Buffer.concat([decipher.update(Buffer.from(envelope.ciphertext,'base64url')),decipher.final()]).toString()),s=payload.result?.structuredContent,m=s?.selectedManifest;
|
|
12
|
+
if(payload.version!==version||payload.accountProjectVerified!==true||payload.executionAllowed!==false||typeof payload.conversationId!=='string'||typeof payload.receiptId!=='string'||payload.projectId!==m?.projectId||payload.projectId!==s?.projectState?.projectId||payload.revision!==s.revision||m.revision!==s.revision||s.executed!==false||m.executionAllowed!==false||m.authorizesSpending!==false||m.authorizesInstallation!==false)throw Error('Website selection is not an exact inert project manifest');
|
|
13
|
+
if(this.receipt&&(payload.projectId!==this.receipt.projectId||payload.conversationId!==this.receipt.conversationId||s.projectState.originalGoal!==this.receipt.result.structuredContent.projectState.originalGoal))throw Error('Account project or original conversation changed');
|
|
14
|
+
if(typeof payload.ackNonce!=='string'||!/^[A-Za-z0-9_-]{43}$/.test(payload.ackNonce))throw Error('Missing exact delivery acknowledgment');this.pendingAck={id:this.link.id,sequence:envelope.sequence,nonce:payload.ackNonce};delete payload.ackNonce;this.sequence=envelope.sequence;this.receipt=payload;return payload;
|
|
15
|
+
}
|
|
16
|
+
async receive({waitSeconds=45}={}){if(!Number.isInteger(waitSeconds)||waitSeconds<0||waitSeconds>45)throw Error('Wait up to 45 seconds');if(!this.link)return {state:'not-open',instructions:'Show the visual chooser with your goal first.'};if(this.pendingAck){await this.request('ack',this.pendingAck);this.pendingAck=null;return {state:'received',...this.receipt};}const end=this.clock()+waitSeconds*1000;do{const value=await this.request('poll',{id:this.link.id,afterSequence:this.sequence});if(value.version!==version||!['waiting','ready'].includes(value.state))throw Error('Unknown website return state');if(value.state==='ready'){const receipt=this.decrypt(value.envelope);try{await this.request('ack',this.pendingAck);this.pendingAck=null;return {state:'received',...receipt};}catch{return {state:'received-acknowledgment-unconfirmed',...receipt};}}if(this.clock()>=end)break;await this.sleep(Math.min(2000,end-this.clock()));}while(this.clock()<=end);return {state:'waiting',chooserUrl:this.link.chooserUrl,pairCode:this.link.pairCode,conversationId:this.receipt?.conversationId??null,lastReceipt:this.receipt??null};}
|
|
17
|
+
close(){this.keys=null;this.link=null;this.receipt=null;}
|
|
18
|
+
}
|