Skip to content

Commit 5522cfb

Browse files
committed
feat: make it possible to pass signed files to launchApp
Allow apps to launch new instances or other apps with specific files. The files must be passed as file signature objects, ensuring it was a file the app was granted access to through a user action.
1 parent e8d6381 commit 5522cfb

File tree

6 files changed

+84
-4
lines changed

6 files changed

+84
-4
lines changed

src/gui/src/helpers/launch_app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ const launch_app = async (options)=>{
201201

202202
// add app_id to URL
203203
iframe_url.searchParams.append('puter.app.id', app_info.uuid);
204+
iframe_url.searchParams.append('puter.app.name', app_info.name);
204205

205206
// add parent_app_instance_id to URL
206207
if (options.parent_instance_id) {
@@ -223,7 +224,7 @@ const launch_app = async (options)=>{
223224

224225
if(file_signature){
225226
iframe_url.searchParams.append('puter.item.uid', file_signature.uid);
226-
iframe_url.searchParams.append('puter.item.path', privacy_aware_path(options.file_path) || file_signature.path);
227+
iframe_url.searchParams.append('puter.item.path', options.file_path ? privacy_aware_path(options.file_path) : file_signature.path);
227228
iframe_url.searchParams.append('puter.item.name', file_signature.fsentry_name);
228229
iframe_url.searchParams.append('puter.item.read_url', file_signature.read_url);
229230
iframe_url.searchParams.append('puter.item.write_url', file_signature.write_url);

src/gui/src/services/ExecService.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class ExecService extends Service {
4848
}
4949

5050
// This method is exposed to apps via IPCService.
51-
async launchApp ({ app_name, args, pseudonym, file_paths }, { ipc_context, msg_id } = {}) {
51+
async launchApp ({ app_name, args, pseudonym, file_paths, items }, { ipc_context, msg_id } = {}) {
5252
const app = ipc_context?.caller?.app;
5353
const process = ipc_context?.caller?.process;
5454

@@ -105,6 +105,13 @@ export class ExecService extends Service {
105105
parent_pseudo_id: connection.backward.uuid,
106106
} : {}),
107107
};
108+
109+
if ( items && items.length ) {
110+
if ( items.length > 1 ) {
111+
console.warn('launchApp does not support launch with multiple items (yet)');
112+
}
113+
launch_options.file_signature = items[0];
114+
}
108115

109116
// Check if file_paths are provided and caller has godmode permissions
110117
if (file_paths && Array.isArray(file_paths) && file_paths.length > 0 && process) {

src/puter-js/src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ export default globalThis.puter = (function() {
215215
if(URLParams.has('puter.app.id')){
216216
this.appID = decodeURIComponent(URLParams.get('puter.app.id'));
217217
}
218+
219+
// Extract app name (added later)
220+
if(URLParams.has('puter.app.name')){
221+
this.appName = decodeURIComponent(URLParams.get('puter.app.name'));
222+
}
218223

219224
// Construct this App's AppData path based on the appID. AppData path is used to store files that are specific to this app.
220225
// The default AppData path is `~/AppData/<appID>`.

src/puter-js/src/modules/FSItem.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,49 @@ class FSItem{
1515
this.modified = options.modified ?? options.fsentry_modified;
1616
this.created = options.created ?? options.fsentry_created;
1717
this.isDirectory = (options.isDirectory || options.is_dir || options.fsentry_is_dir) ? true : false;
18+
19+
// We add some properties to '_internalProperties' to make it clear
20+
// that they are not meant to be accessed outside of puter.js;
21+
// this permits us to change or remove these properties in the future.
22+
const internalProperties = {};
23+
Object.defineProperty(this, '_internalProperties', {
24+
enumerable: false,
25+
value: internalProperties,
26+
});
27+
28+
// Currently 'signature' and 'expires' are not provided in 'options',
29+
// but they can be inferred by writeURL or readURL.
30+
internalProperties.signature = options.signature ?? (() => {
31+
const url = new URL(this.writeURL ?? this.readURL);
32+
return url.searchParams.get('signature');
33+
})();
34+
internalProperties.expires = options.expires ?? (() => {
35+
const url = new URL(this.writeURL ?? this.readURL);
36+
return url.searchParams.get('expires');
37+
})();
38+
39+
// This computed property gives us an object in the format output by
40+
// the `/sign` endpoint, which can be passed to `launch_app` to
41+
// allow apps to open a file in another app or another instance.
42+
Object.defineProperty(internalProperties, 'file_signature', {
43+
get: () => ({
44+
read_url: this.readURL,
45+
write_url: this.writeURL,
46+
metadata_url: this.metadataURL,
47+
fsentry_accessed: this.accessed,
48+
fsentry_modified: this.modified,
49+
fsentry_created: this.created,
50+
fsentry_is_dir: this.isDirectory,
51+
fsentry_size: this.size,
52+
fsentry_name: this.name,
53+
path: this.path,
54+
uid: this.uid,
55+
// /sign outputs another property called "type", but we don't
56+
// have that information here, so it's omitted.
57+
})
58+
});
1859
}
19-
60+
2061
write = async function(data){
2162
return puter.fs.write(
2263
this.path,

src/puter-js/src/modules/FileSystem/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import symlink from './operations/symlink.js';
1515
// a reserved keyword in javascript
1616
import deleteFSEntry from "./operations/deleteFSEntry.js";
1717
import { AdvancedBase } from '../../../../putility/index.js';
18+
import FSItem from '../FSItem.js';
1819

1920
export class PuterJSFileSystemModule extends AdvancedBase {
2021

@@ -31,6 +32,8 @@ export class PuterJSFileSystemModule extends AdvancedBase {
3132
write = write;
3233
sign = sign;
3334
symlink = symlink;
35+
36+
FSItem = FSItem
3437

3538
static NARI_METHODS = {
3639
stat: {

src/puter-js/src/modules/UI.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,9 +1072,18 @@ class UI extends EventListener {
10721072
}
10731073

10741074
// Returns a Promise<AppConnection>
1075-
launchApp = async function launchApp(app_name, args, callback) {
1075+
/**
1076+
* launchApp opens the specified app in Puter with the specified argumets.
1077+
* @param {*} nameOrOptions - name of the app as a string, or an options object
1078+
* @param {*} args - named parameters that will be passed to the app as arguments
1079+
* @param {*} callback - in case you don't want to use `await` or `.then()`
1080+
* @returns
1081+
*/
1082+
launchApp = async function launchApp(nameOrOptions, args, callback) {
10761083
let pseudonym = undefined;
10771084
let file_paths = undefined;
1085+
let items = undefined;
1086+
let app_name = nameOrOptions; // becomes string after branch below
10781087

10791088
// Handle case where app_name is an options object
10801089
if (typeof app_name === 'object' && app_name !== null) {
@@ -1084,17 +1093,31 @@ class UI extends EventListener {
10841093
args = args || options.args;
10851094
callback = callback || options.callback;
10861095
pseudonym = options.pseudonym;
1096+
items = options.items;
1097+
}
1098+
1099+
if ( items ) {
1100+
if ( ! Array.isArray(items) ) items = [];
1101+
for ( let i=0 ; i < items.length ; i++ ) {
1102+
if ( items[i] instanceof FSItem ) {
1103+
items[i] = items[i]._internalProperties.file_signature;
1104+
}
1105+
}
10871106
}
10881107

10891108
if ( app_name && app_name.includes('#(as)') ) {
10901109
[app_name, pseudonym] = app_name.split('#(as)');
10911110
}
1111+
1112+
if ( ! app_name ) app_name = puter.appName;
1113+
10921114
const app_info = await this.#ipc_stub({
10931115
method: 'launchApp',
10941116
callback,
10951117
parameters: {
10961118
app_name,
10971119
file_paths,
1120+
items,
10981121
pseudonym,
10991122
args,
11001123
},

0 commit comments

Comments
 (0)