Skip to content

Commit 183c727

Browse files
authored
Merge pull request #1851 from gofractally/improve-user-prompt-implementation
Improve user prompt implementation
2 parents bc4a5d8 + 1a3301f commit 183c727

4 files changed

Lines changed: 109 additions & 69 deletions

File tree

packages/user/Supervisor/ui/src/constants.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/user/Supervisor/ui/src/plugin/plugin-host.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { REDIRECT_ERROR_CODE } from "@/constants";
2-
31
import {
42
QualifiedDynCallArgs,
53
QualifiedFunctionCallArgs,
@@ -14,7 +12,7 @@ import {
1412
HttpResponse,
1513
} from "../host-interface";
1614
import { Supervisor } from "../supervisor";
17-
import { chainId, isEmbedded, networkName } from "../utils";
15+
import { chainId, networkName } from "../utils";
1816
import { RecoverableErrorPayload } from "./errors";
1917

2018
function convert(
@@ -228,16 +226,7 @@ export class PluginHost implements HostInterface {
228226
remove: (duration, key) => this.dbRemove(duration, key),
229227
},
230228
"supervisor:bridge/prompt": {
231-
requestPrompt: () => {
232-
if (isEmbedded) {
233-
throw this.recoverableError(
234-
"Cannot prompt in embedded mode",
235-
);
236-
}
237-
const err = this.recoverableError("user_prompt_request");
238-
err.code = REDIRECT_ERROR_CODE;
239-
throw err;
240-
},
229+
requestPrompt: () => this.supervisor.requestPrompt(),
241230
},
242231
};
243232
}

packages/user/Supervisor/ui/src/supervisor.ts

Lines changed: 105 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { pluginId } from "@psibase/common-lib/messaging/plugin-id";
2020

2121
import { AppInterface } from "./app-interface";
2222
import { CallContext } from "./call-context";
23-
import { REDIRECT_ERROR_CODE } from "./constants";
2423
import { getRecoverableError } from "./plugin/errors";
2524
import { PluginLoader } from "./plugin/plugin-loader";
2625
import { Plugins } from "./plugin/plugins";
@@ -49,6 +48,19 @@ const systemPlugins: Array<QualifiedPluginId> = [
4948
pluginId("webcrypto", "plugin"),
5049
];
5150

51+
// Control-flow signal thrown to unwind out of a plugin call.
52+
//
53+
// It extends Error so that JCO-transpiled glue never intercepts it: JCO's
54+
// getErrorPayload() re-throws Error instances rather than converting them into a
55+
// component Result::Err. The one exception is an Error carrying its own "payload"
56+
// property, so this class must NOT define one.
57+
class PromptSignal extends Error {
58+
constructor(readonly kind: "prompt" | "embedded-error" | "preload-error") {
59+
super(`prompt-signal:${kind}`);
60+
this.name = "PromptSignal";
61+
}
62+
}
63+
5264
// The supervisor facilitates all communication
5365
export class Supervisor implements AppInterface {
5466
private plugins: Plugins;
@@ -59,6 +71,8 @@ export class Supervisor implements AppInterface {
5971

6072
private embedder: string | undefined;
6173

74+
private inPreload = false;
75+
6276
parser: Promise<any>;
6377

6478
parentOrigination: OriginationData | undefined;
@@ -111,55 +125,72 @@ export class Supervisor implements AppInterface {
111125
}
112126

113127
private async doPreload(plugins: QualifiedPluginId[]) {
114-
await chainIdPromise;
128+
this.inPreload = true;
129+
try {
130+
await chainIdPromise;
115131

116-
if (plugins.length === 0) {
117-
return;
118-
}
132+
if (plugins.length === 0) {
133+
return;
134+
}
119135

120-
// Phase 0: Loads systemPlugins, including those needed to get current user, i.e., accounts, host:auth
121-
this.loader.trackPlugins([...systemPlugins]);
122-
await this.loader.processPlugins();
123-
await this.loader.awaitReady();
136+
// Phase 0: Loads systemPlugins, including those needed to get current user, i.e., accounts, host:auth
137+
this.loader.trackPlugins([...systemPlugins]);
138+
await this.loader.processPlugins();
139+
await this.loader.awaitReady();
124140

125-
// Required to instantiate system plugins to execute the plugin calls below
126-
await this.plugins.instantiateAll();
141+
// Required to instantiate system plugins to execute the plugin calls below
142+
await this.plugins.instantiateAll();
127143

128-
if (isEmbedded) {
129-
const promptDetails = await this.supervisorCall(
130-
getCallArgs("host", "prompt", "admin", "get-active-prompt", []),
131-
);
132-
if (promptDetails) {
133-
this.embedder = promptDetails.activeApp;
134-
delete this.context; // A new one will be created with the embedder
144+
if (isEmbedded) {
145+
const promptDetails = await this.supervisorCall(
146+
getCallArgs(
147+
"host",
148+
"prompt",
149+
"admin",
150+
"get-active-prompt",
151+
[],
152+
),
153+
);
154+
if (promptDetails) {
155+
this.embedder = promptDetails.activeApp;
156+
delete this.context; // A new one will be created with the embedder
157+
}
135158
}
136-
}
137159

138-
setQueryToken(this.getActiveQueryToken());
139-
140-
// Phase 1: Compile app plugins (NO instantiation yet — Memory deferred).
141-
// The sync call to getAuthServices below only touches Phase 0 plugins.
142-
this.loader.trackPlugins([...plugins]);
143-
await this.loader.processPlugins();
144-
await this.loader.awaitReady();
160+
setQueryToken(this.getActiveQueryToken());
161+
162+
// Phase 1: Compile app plugins (NO instantiation yet — Memory deferred).
163+
// The sync call to getAuthServices below only touches Phase 0 plugins.
164+
this.loader.trackPlugins([...plugins]);
165+
await this.loader.processPlugins();
166+
await this.loader.awaitReady();
167+
168+
// Phase 2: Load the auth services for all connected accounts.
169+
// This sync call uses accounts:plugin (Phase 0, already instantiated).
170+
const auth_services: string[] = this.supervisorCall(
171+
getCallArgs(
172+
"accounts",
173+
"plugin",
174+
"admin",
175+
"get-auth-services",
176+
[],
177+
),
178+
);
145179

146-
// Phase 2: Load the auth services for all connected accounts.
147-
// This sync call uses accounts:plugin (Phase 0, already instantiated).
148-
const auth_services: string[] = this.supervisorCall(
149-
getCallArgs("accounts", "plugin", "admin", "get-auth-services", []),
150-
);
180+
const addtl_plugins: QualifiedPluginId[] = [];
181+
for (const service of auth_services) {
182+
if (!service) continue;
151183

152-
const addtl_plugins: QualifiedPluginId[] = [];
153-
for (const service of auth_services) {
154-
if (!service) continue;
184+
// Current limitation: an auth service plugin must be called "plugin" ("<service>:plugin")
185+
addtl_plugins.push(pluginId(service, "plugin"));
186+
}
187+
this.loader.trackPlugins(addtl_plugins);
155188

156-
// Current limitation: an auth service plugin must be called "plugin" ("<service>:plugin")
157-
addtl_plugins.push(pluginId(service, "plugin"));
189+
await this.loader.processPlugins();
190+
await this.loader.awaitReady();
191+
} finally {
192+
this.inPreload = false;
158193
}
159-
this.loader.trackPlugins(addtl_plugins);
160-
161-
await this.loader.processPlugins();
162-
await this.loader.awaitReady();
163194
}
164195

165196
private replyToParent(id: string, result: any) {
@@ -270,6 +301,13 @@ export class Supervisor implements AppInterface {
270301
);
271302
}
272303

304+
requestPrompt(): never {
305+
if (this.inPreload) {
306+
throw new PromptSignal("preload-error");
307+
}
308+
throw new PromptSignal(isEmbedded ? "embedded-error" : "prompt");
309+
}
310+
273311
call(args: QualifiedFunctionCallArgs): any {
274312
assertTruthy(this.context, "Uninitialized call context");
275313

@@ -431,21 +469,35 @@ export class Supervisor implements AppInterface {
431469
// Send plugin result to parent window
432470
this.replyToParent(id, result);
433471
} catch (e) {
434-
const err = getRecoverableError(e);
435-
if (err) {
436-
let newError;
437-
if (err.code === REDIRECT_ERROR_CODE) {
438-
newError = new RedirectErrorObject(
439-
err.producer,
440-
err.message,
441-
);
442-
} else {
443-
newError = new PluginErrorObject(err.producer, err.message);
444-
}
445-
this.replyToParent(id, newError);
472+
let result: any;
473+
if (e instanceof PromptSignal && e.kind === "prompt") {
474+
result = new RedirectErrorObject(
475+
{ service: "host", plugin: "prompt" },
476+
"user_prompt_request",
477+
);
478+
} else if (
479+
e instanceof PromptSignal &&
480+
e.kind === "embedded-error"
481+
) {
482+
result = new PluginErrorObject(
483+
{ service: "host", plugin: "prompt" },
484+
"Cannot prompt in embedded mode",
485+
);
486+
} else if (
487+
e instanceof PromptSignal &&
488+
e.kind === "preload-error"
489+
) {
490+
result = new PluginErrorObject(
491+
{ service: "host", plugin: "prompt" },
492+
"Cannot trigger user prompt during plugin preload",
493+
);
446494
} else {
447-
this.replyToParent(id, e);
495+
const err = getRecoverableError(e);
496+
result = err
497+
? new PluginErrorObject(err.producer, err.message)
498+
: e;
448499
}
500+
this.replyToParent(id, result);
449501
} finally {
450502
this.plugins.disposeAll();
451503
this.cleanupSessionState();

rust/psibase_plugin/src/wasm/trust.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,11 @@ pub fn authorized_with_whitelist<T: TrustConfig + ?Sized>(
156156
) -> Result<bool, Error> {
157157
let whitelist: Vec<String> = whitelist.iter().map(|s| s.to_string()).collect();
158158
let descriptions = T::get_descriptions();
159-
Ok(permissions::api::is_authorized(
159+
permissions::api::is_authorized(
160160
&host::client::get_sender(),
161161
level,
162162
&descriptions,
163163
fn_name,
164164
&whitelist,
165-
)?)
165+
)
166166
}

0 commit comments

Comments
 (0)