mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
Add puter.js
This commit is contained in:
parent
fc5025a2a8
commit
b8e66cada9
@ -9,6 +9,30 @@ class SelfhostedModule extends AdvancedBase {
|
||||
|
||||
const ComplainAboutVersionsService = require('./services/ComplainAboutVersionsService');
|
||||
services.registerService('complain-about-versions', ComplainAboutVersionsService);
|
||||
|
||||
const DevWatcherService = require('./services/DevWatcherService');
|
||||
const path_ = require('path');
|
||||
services.registerService('__dev-watcher', DevWatcherService, {
|
||||
root: path_.resolve(__dirname, '../../../'),
|
||||
commands: [
|
||||
{
|
||||
name: 'puter.js:webpack-watch',
|
||||
directory: 'packages/puter-dot-js',
|
||||
command: 'npm',
|
||||
args: ['run', 'start-webpack'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const ServeStaticFilesService = require("./services/ServceStaticFilesService");
|
||||
services.registerService('__serve-puterjs', ServeStaticFilesService, {
|
||||
directories: [
|
||||
{
|
||||
prefix: '/sdk',
|
||||
path: path_.resolve(__dirname, '../../../packages/puter-dot-js/dist'),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
93
packages/backend/src/services/DevWatcherService.js
Normal file
93
packages/backend/src/services/DevWatcherService.js
Normal file
@ -0,0 +1,93 @@
|
||||
const BaseService = require("./BaseService");
|
||||
|
||||
class ProxyLogger {
|
||||
constructor (log) {
|
||||
this.log = log;
|
||||
}
|
||||
attach (stream) {
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk.toString();
|
||||
let lineEndIndex = buffer.indexOf('\n');
|
||||
while (lineEndIndex !== -1) {
|
||||
const line = buffer.substring(0, lineEndIndex);
|
||||
this.log(line);
|
||||
buffer = buffer.substring(lineEndIndex + 1);
|
||||
lineEndIndex = buffer.indexOf('\n');
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if (buffer.length) {
|
||||
this.log(buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* This service is used to run webpack watchers.
|
||||
*/
|
||||
class DevWatcherService extends BaseService {
|
||||
static MODULES = {
|
||||
path: require('path'),
|
||||
spawn: require('child_process').spawn,
|
||||
};
|
||||
|
||||
_construct () {
|
||||
this.instances = [];
|
||||
}
|
||||
|
||||
async _init (args) {
|
||||
const { root, commands } = args;
|
||||
|
||||
process.on('exit', () => {
|
||||
this.exit_all_();
|
||||
})
|
||||
|
||||
for ( const entry of commands ) {
|
||||
const { name, directory, command, args } = entry;
|
||||
const fullpath = this.modules.path.join(
|
||||
root, directory);
|
||||
this.start_({ name, fullpath, command, args });
|
||||
}
|
||||
}
|
||||
|
||||
log_ (name, isErr, line) {
|
||||
let txt = `[${name}:`;
|
||||
txt += isErr
|
||||
? `\x1B[31;1merr\x1B[0m`
|
||||
: `\x1B[32;1mout\x1B[0m`;
|
||||
txt += '] ' + line;
|
||||
this.log.info(txt);
|
||||
}
|
||||
|
||||
async start_ ({ name, fullpath, command, args }) {
|
||||
this.log.info(`Starting ${name} in ${fullpath}`);
|
||||
const proc = this.modules.spawn(command, args, {
|
||||
shell: true,
|
||||
env: process.env,
|
||||
cwd: fullpath,
|
||||
});
|
||||
this.instances.push({
|
||||
name, proc,
|
||||
});
|
||||
const out = new ProxyLogger((line) => this.log_(name, false, line));
|
||||
out.attach(proc.stdout);
|
||||
const err = new ProxyLogger((line) => this.log_(name, true, line));
|
||||
err.attach(proc.stderr);
|
||||
proc.on('exit', () => {
|
||||
this.log.info(`[${name}:exit] Process exited (${proc.exitCode})`);
|
||||
this.instances = this.instances.filter((inst) => inst.proc !== proc);
|
||||
})
|
||||
}
|
||||
|
||||
async exit_all_ () {
|
||||
for ( const { proc } of this.instances ) {
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DevWatcherService;
|
17
packages/backend/src/services/ServceStaticFilesService.js
Normal file
17
packages/backend/src/services/ServceStaticFilesService.js
Normal file
@ -0,0 +1,17 @@
|
||||
const BaseService = require("./BaseService");
|
||||
|
||||
class ServeStaticFilesService extends BaseService {
|
||||
async _init (args) {
|
||||
this.directories = args.directories;
|
||||
}
|
||||
|
||||
async ['__on_install.routes'] () {
|
||||
const { app } = this.services.get('web-server');
|
||||
|
||||
for ( const { prefix, path } of this.directories ) {
|
||||
app.use(prefix, require('express').static(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServeStaticFilesService;
|
19
packages/backend/src/util/configutil.js
Normal file
19
packages/backend/src/util/configutil.js
Normal file
@ -0,0 +1,19 @@
|
||||
let memoized_common_template_vars_ = null;
|
||||
const get_common_template_vars = () => {
|
||||
const path_ = require('path');
|
||||
if ( memoized_common_template_vars_ !== null ) {
|
||||
return memoized_common_template_vars_;
|
||||
}
|
||||
|
||||
const code_root = path_.resolve(__dirname, '../../');
|
||||
|
||||
memoized_common_template_vars_ = {
|
||||
code_root,
|
||||
};
|
||||
|
||||
return memoized_common_template_vars_;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get_common_template_vars,
|
||||
};
|
137
packages/puter-dot-js/.gitignore
vendored
Normal file
137
packages/puter-dot-js/.gitignore
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
# MAC OS hidden directory settings file
|
||||
.DS_Store
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||
*.zip
|
||||
*.pem
|
||||
.DS_Store
|
||||
./build
|
||||
build
|
||||
|
||||
# config file
|
||||
src/config.js
|
||||
ssl
|
||||
ssl/
|
201
packages/puter-dot-js/APACHE_LICENSE.txt
Normal file
201
packages/puter-dot-js/APACHE_LICENSE.txt
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2024 Puter Technologies Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
55
packages/puter-dot-js/README.md
Normal file
55
packages/puter-dot-js/README.md
Normal file
@ -0,0 +1,55 @@
|
||||
<h3 align="center">Puter.js</h3>
|
||||
<h4 align="center">The official JavaScript SDK for Puter.com. Cloud and AI features right from your frontend code!</h4>
|
||||
<p align="center">
|
||||
<a href="https://docs.puter.com/playground/"><strong>« LIVE DEMO »</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://docs.puter.com" target="_blank">Docs</a>
|
||||
·
|
||||
<a href="https://puter.com">Puter.com</a>
|
||||
·
|
||||
<a href="https://discord.com/invite/PQcx7Teh8u">Discord</a>
|
||||
·
|
||||
<a href="https://reddit.com/r/puter">Reddit</a>
|
||||
·
|
||||
<a href="https://twitter.com/HeyPuter">X (Twitter)</a>
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
```
|
||||
git clone https://github.com/HeyPuter/puter.js.git
|
||||
cd puter.js
|
||||
npm install
|
||||
```
|
||||
|
||||
## Run development server
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Example
|
||||
Make sure the development server is running.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="http://127.0.0.1:8080/dist/puter.dev.js"></script>
|
||||
<script>
|
||||
// Loading ...
|
||||
puter.print(`Loading...`);
|
||||
|
||||
// Chat with GPT-3.5 Turbo
|
||||
puter.ai.chat(`What color was Napoleon's white horse?`).then((response) => {
|
||||
puter.print(response);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
12
packages/puter-dot-js/app-migration-guide.txt
Normal file
12
packages/puter-dot-js/app-migration-guide.txt
Normal file
@ -0,0 +1,12 @@
|
||||
all UI function calls from puter should be in the form `puter.ui.<function>`
|
||||
e.g. `puter.showOpenFilePicker` becomes `puter.ui.showOpenFilePicker`
|
||||
|
||||
puter.FileSystem.<function> -> puter.fs.<function>
|
||||
puter.Router.<function> -> puter.router.<function>
|
||||
puter.Apps.<function> -> puter.apps.<function>
|
||||
|
||||
puter.setItem -> puter.kv.set
|
||||
puter.getItem -> puter.kv.get
|
||||
puter.removeItem -> puter.kv.del
|
||||
puter.createCloudItem(...) -> new puter.CloudItem(...)
|
||||
puter.router.* -> puter.hosting.*
|
1738
packages/puter-dot-js/package-lock.json
generated
Normal file
1738
packages/puter-dot-js/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
packages/puter-dot-js/package.json
Normal file
19
packages/puter-dot-js/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "puter",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start-server": "npx http-server --cors -c-1",
|
||||
"start-webpack": "export NODE_OPTIONS=--openssl-legacy-provider && webpack ./src/index.js --output-filename puter.js && webpack ./src/index.js --output-filename puter.dev.js --watch --devtool source-map",
|
||||
"start": "concurrently \"npm run start-server\" \"npm run start-webpack\"",
|
||||
"build": "export NODE_OPTIONS=--openssl-legacy-provider && webpack ./src/index.js --output-filename puter.js && { echo \"// Copyright 2024 Puter Technologies Inc. All rights reserved.\"; echo \"// Generated on $(date '+%Y-%m-%d %H:%M')\n\"; cat ./dist/puter.js; } > temp && mv temp ./dist/puter.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
BIN
packages/puter-dot-js/src/bg.png
Normal file
BIN
packages/puter-dot-js/src/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 302 KiB |
BIN
packages/puter-dot-js/src/bg.webp
Normal file
BIN
packages/puter-dot-js/src/bg.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
358
packages/puter-dot-js/src/index.js
Normal file
358
packages/puter-dot-js/src/index.js
Normal file
@ -0,0 +1,358 @@
|
||||
import OS from './modules/OS.js';
|
||||
import FileSystem from './modules/FileSystem/index.js';
|
||||
import Hosting from './modules/Hosting.js';
|
||||
import Apps from './modules/Apps.js';
|
||||
import UI from './modules/UI.js';
|
||||
import KV from './modules/KV.js';
|
||||
import AI from './modules/AI.js';
|
||||
import Auth from './modules/Auth.js';
|
||||
import FSItem from './modules/FSItem.js';
|
||||
import * as utils from './lib/utils.js';
|
||||
import path from './lib/path.js';
|
||||
|
||||
window.puter = (function() {
|
||||
'use strict';
|
||||
|
||||
class Puter{
|
||||
// The environment that the SDK is running in. Can be 'gui', 'app' or 'web'.
|
||||
// 'gui' means the SDK is running in the Puter GUI, i.e. Puter.com.
|
||||
// 'app' means the SDK is running as a Puter app, i.e. within an iframe in the Puter GUI.
|
||||
// 'web' means the SDK is running in a 3rd-party website.
|
||||
env;
|
||||
|
||||
defaultAPIOrigin = 'https://api.puter.com';
|
||||
defaultGUIOrigin = 'https://puter.com';
|
||||
|
||||
// An optional callback when the user is authenticated. This can be set by the app using the SDK.
|
||||
onAuth;
|
||||
|
||||
/**
|
||||
* State object to keep track of the authentication request status.
|
||||
* This is used to prevent multiple authentication popups from showing up by different parts of the app.
|
||||
*/
|
||||
puterAuthState = {
|
||||
isPromptOpen: false,
|
||||
authGranted: null,
|
||||
resolver: null
|
||||
};
|
||||
|
||||
// Holds the unique app instance ID that is provided by the host environment
|
||||
appInstanceID;
|
||||
|
||||
// Holds the unique app instance ID for the parent (if any), which is provided by the host environment
|
||||
parentInstanceID;
|
||||
|
||||
// Expose the FSItem class
|
||||
static FSItem = FSItem;
|
||||
|
||||
// Event handling properties
|
||||
eventHandlers = {};
|
||||
|
||||
// --------------------------------------------
|
||||
// Constructor
|
||||
// --------------------------------------------
|
||||
constructor(options) {
|
||||
options = options ?? {};
|
||||
|
||||
// Holds the query parameters found in the current URL
|
||||
let URLParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Figure out the environment in which the SDK is running
|
||||
if (URLParams.has('puter.app_instance_id'))
|
||||
this.env = 'app';
|
||||
else if(window.puter_gui_enabled === true)
|
||||
this.env = 'gui';
|
||||
else
|
||||
this.env = 'web';
|
||||
|
||||
// there are some specific situations where puter is definitely loaded in GUI mode
|
||||
// we're going to check for those situations here so that we don't break anything unintentionally
|
||||
// if navigator URL's hostname is 'puter.com'
|
||||
if(window.location.hostname === 'puter.com'){
|
||||
this.env = 'gui';
|
||||
}
|
||||
|
||||
// Get the 'args' from the URL. This is used to pass arguments to the app.
|
||||
if(URLParams.has('puter.args')){
|
||||
this.args = JSON.parse(decodeURIComponent(URLParams.get('puter.args')));
|
||||
}else{
|
||||
this.args = {};
|
||||
}
|
||||
|
||||
// Try to extract appInstanceID from the URL. appInstanceID is included in every messaage
|
||||
// sent to the host environment. This is used to help host environment identify the app
|
||||
// instance that sent the message and communicate back to it.
|
||||
if(URLParams.has('puter.app_instance_id')){
|
||||
this.appInstanceID = decodeURIComponent(URLParams.get('puter.app_instance_id'));
|
||||
}
|
||||
|
||||
// Try to extract parentInstanceID from the URL. If another app launched this app instance, parentInstanceID
|
||||
// holds its instance ID, and is used to communicate with that parent app.
|
||||
if(URLParams.has('puter.parent_instance_id')){
|
||||
this.parentInstanceID = decodeURIComponent(URLParams.get('puter.parent_instance_id'));
|
||||
}
|
||||
|
||||
// Try to extract `puter.app.id` from the URL. `puter.app.id` is the unique ID of the app.
|
||||
// App ID is useful for identifying the app when communicating with the Puter API, among other things.
|
||||
if(URLParams.has('puter.app.id')){
|
||||
this.appID = decodeURIComponent(URLParams.get('puter.app.id'));
|
||||
}
|
||||
|
||||
// Construct this App's AppData path based on the appID. AppData path is used to store files that are specific to this app.
|
||||
// The default AppData path is `~/AppData/<appID>`.
|
||||
if(this.appID){
|
||||
this.appDataPath = `~/AppData/${this.appID}`;
|
||||
}
|
||||
|
||||
// Construct APIOrigin from the URL. APIOrigin is used to build the URLs for the Puter API endpoints.
|
||||
// The default APIOrigin is https://api.puter.com. However, if the URL contains a `puter.api_origin` query parameter,
|
||||
// then that value is used as the APIOrigin. If the URL contains a `puter.domain` query parameter, then the APIOrigin
|
||||
// is constructed as `https://api.<puter.domain>`.
|
||||
this.APIOrigin = this.defaultAPIOrigin;
|
||||
if(URLParams.has('puter.api_origin')){
|
||||
this.APIOrigin = decodeURIComponent(URLParams.get('puter.api_origin'));
|
||||
}else if(URLParams.has('puter.domain')){
|
||||
this.APIOrigin = 'https://api.' + URLParams.get('puter.domain');
|
||||
}
|
||||
|
||||
// The SDK is running in the Puter GUI (i.e. 'gui')
|
||||
if(this.env === 'gui'){
|
||||
this.authToken = window.auth_token;
|
||||
// initialize submodules
|
||||
this.initSubmodules();
|
||||
}
|
||||
// Loaded in an iframe in the Puter GUI (i.e. 'app')
|
||||
// When SDK is loaded in App mode the initiation process should start when the DOM is ready
|
||||
else if (this.env === 'app') {
|
||||
this.authToken = decodeURIComponent(URLParams.get('puter.auth.token'));
|
||||
// initialize submodules
|
||||
this.initSubmodules();
|
||||
// If the authToken is already set in localStorage, then we don't need to show the dialog
|
||||
try {
|
||||
if(localStorage.getItem('puter.auth.token')){
|
||||
this.setAuthToken(localStorage.getItem('puter.auth.token'));
|
||||
}
|
||||
// if appID is already set in localStorage, then we don't need to show the dialog
|
||||
if(localStorage.getItem('puter.app.id')){
|
||||
this.setAppID(localStorage.getItem('puter.app.id'));
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
console.error('Error accessing localStorage:', error);
|
||||
}
|
||||
}
|
||||
// SDK was loaded in a 3rd-party website.
|
||||
// When SDK is loaded in GUI the initiation process should start when the DOM is ready. This is because
|
||||
// the SDK needs to show a dialog to the user to ask for permission to access their Puter account.
|
||||
else if(this.env === 'web') {
|
||||
// initialize submodules
|
||||
this.initSubmodules();
|
||||
try{
|
||||
// If the authToken is already set in localStorage, then we don't need to show the dialog
|
||||
if(localStorage.getItem('puter.auth.token')){
|
||||
this.setAuthToken(localStorage.getItem('puter.auth.token'));
|
||||
}
|
||||
// if appID is already set in localStorage, then we don't need to show the dialog
|
||||
if(localStorage.getItem('puter.app.id')){
|
||||
this.setAppID(localStorage.getItem('puter.app.id'));
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
console.error('Error accessing localStorage:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize submodules
|
||||
initSubmodules = function(){
|
||||
// Auth
|
||||
this.auth = new Auth(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// OS
|
||||
this.os = new OS(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// FileSystem
|
||||
this.fs = new FileSystem(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// UI
|
||||
this.ui = new UI(this.appInstanceID, this.parentInstanceID, this.appID, this.env);
|
||||
// Hosting
|
||||
this.hosting = new Hosting(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// Apps
|
||||
this.apps = new Apps(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// AI
|
||||
this.ai = new AI(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// Key-Value Store
|
||||
this.kv = new KV(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||
// Path
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
updateSubmodules() {
|
||||
// Update submodules with new auth token and API origin
|
||||
[this.os, this.fs, this.hosting, this.apps, this.ai, this.kv].forEach(module => {
|
||||
if(!module) return;
|
||||
module.setAuthToken(this.authToken);
|
||||
module.setAPIOrigin(this.APIOrigin);
|
||||
});
|
||||
}
|
||||
|
||||
setAppID = function (appID) {
|
||||
// save to localStorage
|
||||
try{
|
||||
localStorage.setItem('puter.app.id', appID);
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
console.error('Error accessing localStorage:', error);
|
||||
}
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
setAuthToken = function (authToken) {
|
||||
this.authToken = authToken;
|
||||
// If the SDK is running on a 3rd-party site or an app, then save the authToken in localStorage
|
||||
if(this.env === 'web' || this.env === 'app'){
|
||||
try{
|
||||
localStorage.setItem('puter.auth.token', authToken);
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
console.error('Error accessing localStorage:', error);
|
||||
}
|
||||
}
|
||||
// reinitialize submodules
|
||||
this.updateSubmodules();
|
||||
}
|
||||
|
||||
setAPIOrigin = function (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
// reinitialize submodules
|
||||
this.updateSubmodules();
|
||||
}
|
||||
|
||||
resetAuthToken = function () {
|
||||
this.authToken = null;
|
||||
// If the SDK is running on a 3rd-party site or an app, then save the authToken in localStorage
|
||||
if(this.env === 'web' || this.env === 'app'){
|
||||
try{
|
||||
localStorage.removeItem('puter.auth.token');
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
console.error('Error accessing localStorage:', error);
|
||||
}
|
||||
}
|
||||
// reinitialize submodules
|
||||
this.updateSubmodules();
|
||||
}
|
||||
|
||||
exit = function() {
|
||||
window.parent.postMessage({
|
||||
msg: "exit",
|
||||
appInstanceID: this.appInstanceID,
|
||||
}, '*');
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that generates a domain-safe name by combining a random adjective, a random noun, and a random number (between 0 and 9999).
|
||||
* The result is returned as a string with components separated by hyphens.
|
||||
* It is useful when you need to create unique identifiers that are also human-friendly.
|
||||
*
|
||||
* @param {string} [separateWith='-'] - The character to use to separate the components of the generated name.
|
||||
* @returns {string} A unique, hyphen-separated string comprising of an adjective, a noun, and a number.
|
||||
*
|
||||
*/
|
||||
randName = function(separateWith = '-'){
|
||||
const first_adj = ['helpful','sensible', 'loyal', 'honest', 'clever', 'capable','calm', 'smart', 'genius', 'bright', 'charming', 'creative', 'diligent', 'elegant', 'fancy',
|
||||
'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent', 'jolly', 'kind', 'lively', 'merry', 'nice', 'optimistic', 'polite',
|
||||
'quiet', 'relaxed', 'silly', 'victorious', 'witty', 'young', 'zealous', 'strong', 'brave', 'agile', 'bold'];
|
||||
|
||||
const nouns = ['street', 'roof', 'floor', 'tv', 'idea', 'morning', 'game', 'wheel', 'shoe', 'bag', 'clock', 'pencil', 'pen',
|
||||
'magnet', 'chair', 'table', 'house', 'dog', 'room', 'book', 'car', 'cat', 'tree',
|
||||
'flower', 'bird', 'fish', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'wind', 'mountain',
|
||||
'river', 'lake', 'sea', 'ocean', 'island', 'bridge', 'road', 'train', 'plane', 'ship', 'bicycle',
|
||||
'horse', 'elephant', 'lion', 'tiger', 'bear', 'zebra', 'giraffe', 'monkey', 'snake', 'rabbit', 'duck',
|
||||
'goose', 'penguin', 'frog', 'crab', 'shrimp', 'whale', 'octopus', 'spider', 'ant', 'bee', 'butterfly', 'dragonfly',
|
||||
'ladybug', 'snail', 'camel', 'kangaroo', 'koala', 'panda', 'piglet', 'sheep', 'wolf', 'fox', 'deer', 'mouse', 'seal',
|
||||
'chicken', 'cow', 'dinosaur', 'puppy', 'kitten', 'circle', 'square', 'garden', 'otter', 'bunny', 'meerkat', 'harp']
|
||||
|
||||
// return a random combination of first_adj + noun + number (between 0 and 9999)
|
||||
// e.g. clever-idea-123
|
||||
return first_adj[Math.floor(Math.random() * first_adj.length)] + separateWith + nouns[Math.floor(Math.random() * nouns.length)] + separateWith + Math.floor(Math.random() * 10000);
|
||||
}
|
||||
|
||||
getUser = function(...args){
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = utils.initXhr('/whoami', this.APIOrigin, this.authToken, 'get');
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
|
||||
print = function(...args){
|
||||
for(let arg of args){
|
||||
document.getElementsByTagName('body')[0].append(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create a new Puter object and return it
|
||||
const puterobj = new Puter();
|
||||
|
||||
// Return the Puter object
|
||||
return puterobj;
|
||||
}());
|
||||
|
||||
window.addEventListener('message', async (event) => {
|
||||
// if the message is not from Puter, then ignore it
|
||||
if(event.origin !== puter.defaultGUIOrigin) return;
|
||||
|
||||
if(event.data.msg && event.data.msg === 'requestOrigin'){
|
||||
event.source.postMessage({
|
||||
msg: "originResponse",
|
||||
}, '*');
|
||||
}
|
||||
else if (event.data.msg === 'puter.token') {
|
||||
// puterDialog.close();
|
||||
// Set the authToken property
|
||||
puter.setAuthToken(event.data.token);
|
||||
// update appID
|
||||
puter.setAppID(event.data.app_uid);
|
||||
// Remove the event listener to avoid memory leaks
|
||||
// window.removeEventListener('message', messageListener);
|
||||
|
||||
puter.puterAuthState.authGranted = true;
|
||||
// Resolve the promise
|
||||
// resolve();
|
||||
|
||||
// Call onAuth callback
|
||||
if(puter.onAuth && typeof puter.onAuth === 'function'){
|
||||
puter.getUser().then((user) => {
|
||||
puter.onAuth(user)
|
||||
});
|
||||
}
|
||||
|
||||
puter.puterAuthState.isPromptOpen = false;
|
||||
// Resolve or reject any waiting promises.
|
||||
if (puter.puterAuthState.resolver) {
|
||||
if (puter.puterAuthState.authGranted) {
|
||||
puter.puterAuthState.resolver.resolve();
|
||||
} else {
|
||||
puter.puterAuthState.resolver.reject();
|
||||
}
|
||||
puter.puterAuthState.resolver = null;
|
||||
};
|
||||
}
|
||||
})
|
49
packages/puter-dot-js/src/lib/EventListener.js
Normal file
49
packages/puter-dot-js/src/lib/EventListener.js
Normal file
@ -0,0 +1,49 @@
|
||||
export default class EventListener {
|
||||
// Array of all supported event names.
|
||||
#eventNames;
|
||||
|
||||
// Map of eventName -> array of listeners
|
||||
#eventListeners;
|
||||
|
||||
constructor(eventNames) {
|
||||
this.#eventNames = eventNames;
|
||||
|
||||
this.#eventListeners = (() => {
|
||||
const map = new Map();
|
||||
for (let eventName of this.#eventNames) {
|
||||
map[eventName] = [];
|
||||
}
|
||||
return map;
|
||||
})();
|
||||
}
|
||||
|
||||
emit(eventName, data) {
|
||||
if (!this.#eventNames.includes(eventName)) {
|
||||
console.error(`Event name '${eventName}' not supported`);
|
||||
return;
|
||||
}
|
||||
this.#eventListeners[eventName].forEach((listener) => {
|
||||
listener(data);
|
||||
});
|
||||
}
|
||||
|
||||
on(eventName, callback) {
|
||||
if (!this.#eventNames.includes(eventName)) {
|
||||
console.error(`Event name '${eventName}' not supported`);
|
||||
return;
|
||||
}
|
||||
this.#eventListeners[eventName].push(callback);
|
||||
}
|
||||
|
||||
off(eventName, callback) {
|
||||
if (!this.#eventNames.includes(eventName)) {
|
||||
console.error(`Event name '${eventName}' not supported`);
|
||||
return;
|
||||
}
|
||||
const listeners = this.#eventListeners[eventName];
|
||||
const index = listeners.indexOf(callback)
|
||||
if (index !== -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
509
packages/puter-dot-js/src/lib/path.js
Normal file
509
packages/puter-dot-js/src/lib/path.js
Normal file
@ -0,0 +1,509 @@
|
||||
// import {cwd} from './env.js'
|
||||
let cwd;
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
//'use strict';
|
||||
|
||||
const
|
||||
CHAR_UPPERCASE_A = 65,
|
||||
CHAR_LOWERCASE_A = 97,
|
||||
CHAR_UPPERCASE_Z = 90,
|
||||
CHAR_LOWERCASE_Z = 122,
|
||||
CHAR_DOT = 46,
|
||||
CHAR_FORWARD_SLASH = 47,
|
||||
CHAR_BACKWARD_SLASH = 92,
|
||||
CHAR_COLON = 58,
|
||||
CHAR_QUESTION_MARK = 63;
|
||||
|
||||
function isPathSeparator(code) {
|
||||
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
|
||||
}
|
||||
|
||||
function isPosixPathSeparator(code) {
|
||||
return code === CHAR_FORWARD_SLASH;
|
||||
}
|
||||
|
||||
// Resolves . and .. elements in a path with directory names
|
||||
function normalizeString(path, allowAboveRoot, separator, isPathSeparator) {
|
||||
let res = '';
|
||||
let lastSegmentLength = 0;
|
||||
let lastSlash = -1;
|
||||
let dots = 0;
|
||||
let code = 0;
|
||||
for (let i = 0; i <= path.length; ++i) {
|
||||
if (i < path.length)
|
||||
code = path.charCodeAt(i);
|
||||
else if (isPathSeparator(code))
|
||||
break;
|
||||
else
|
||||
code = CHAR_FORWARD_SLASH;
|
||||
|
||||
if (isPathSeparator(code)) {
|
||||
if (lastSlash === i - 1 || dots === 1) {
|
||||
// NOOP
|
||||
} else if (dots === 2) {
|
||||
if (res.length < 2 || lastSegmentLength !== 2 ||
|
||||
res.charCodeAt( res.length - 1) !== CHAR_DOT ||
|
||||
res.charCodeAt(res.length - 2) !== CHAR_DOT) {
|
||||
if (res.length > 2) {
|
||||
const lastSlashIndex = res.lastIndexOf(separator);
|
||||
if (lastSlashIndex === -1) {
|
||||
res = '';
|
||||
lastSegmentLength = 0;
|
||||
} else {
|
||||
res = res.slice(0, lastSlashIndex);
|
||||
lastSegmentLength =
|
||||
res.length - 1 - res.lastIndexOf(res, separator);
|
||||
}
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
continue;
|
||||
} else if (res.length !== 0) {
|
||||
res = '';
|
||||
lastSegmentLength = 0;
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (allowAboveRoot) {
|
||||
res += res.length > 0 ? `${separator}..` : '..';
|
||||
lastSegmentLength = 2;
|
||||
}
|
||||
} else {
|
||||
if (res.length > 0)
|
||||
res += `${separator}${path.slice(lastSlash + 1, i)}`;
|
||||
else
|
||||
res = path.slice(lastSlash + 1, i);
|
||||
lastSegmentLength = i - lastSlash - 1;
|
||||
}
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
} else if (code === CHAR_DOT && dots !== -1) {
|
||||
++dots;
|
||||
} else {
|
||||
dots = -1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
const path = {
|
||||
// path.resolve([from ...], to)
|
||||
resolve(...args) {
|
||||
let resolvedPath = '';
|
||||
let resolvedAbsolute = false;
|
||||
|
||||
for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
||||
// orig const path = i >= 0 ? args[i] : posixCwd();
|
||||
const path = i >= 0 ? args[i] : (cwd !== undefined ? cwd : '/');
|
||||
// const path = i >= 0 ? args[i] : '/';
|
||||
|
||||
// Skip empty entries
|
||||
if (path.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resolvedPath = `${path}/${resolvedPath}`;
|
||||
resolvedAbsolute =
|
||||
path.charCodeAt(0) === CHAR_FORWARD_SLASH;
|
||||
}
|
||||
|
||||
// At this point the path should be resolved to a full absolute path, but
|
||||
// handle relative paths to be safe (might happen when process.cwd() fails)
|
||||
|
||||
// Normalize the path
|
||||
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/',
|
||||
isPosixPathSeparator);
|
||||
|
||||
if (resolvedAbsolute) {
|
||||
return `/${resolvedPath}`;
|
||||
}
|
||||
return resolvedPath.length > 0 ? resolvedPath : '.';
|
||||
},
|
||||
|
||||
normalize(path) {
|
||||
if (path.length === 0)
|
||||
return '.';
|
||||
|
||||
const isAbsolute =
|
||||
path.charCodeAt(0) === CHAR_FORWARD_SLASH;
|
||||
const trailingSeparator =
|
||||
path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH;
|
||||
|
||||
// Normalize the path
|
||||
path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator);
|
||||
|
||||
if (path.length === 0) {
|
||||
if (isAbsolute)
|
||||
return '/';
|
||||
return trailingSeparator ? './' : '.';
|
||||
}
|
||||
if (trailingSeparator)
|
||||
path += '/';
|
||||
|
||||
return isAbsolute ? `/${path}` : path;
|
||||
},
|
||||
|
||||
isAbsolute(path) {
|
||||
return path.length > 0 &&
|
||||
path.charCodeAt(0) === CHAR_FORWARD_SLASH;
|
||||
},
|
||||
|
||||
join(...args) {
|
||||
if (args.length === 0)
|
||||
return '.';
|
||||
let joined;
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
const arg = args[i];
|
||||
if (arg.length > 0) {
|
||||
if (joined === undefined)
|
||||
joined = arg;
|
||||
else
|
||||
joined += `/${arg}`;
|
||||
}
|
||||
}
|
||||
if (joined === undefined)
|
||||
return '.';
|
||||
return path.normalize(joined);
|
||||
},
|
||||
|
||||
relative(from, to) {
|
||||
if (from === to)
|
||||
return '';
|
||||
|
||||
// Trim leading forward slashes.
|
||||
from = path.resolve(from);
|
||||
to = path.resolve(to);
|
||||
|
||||
if (from === to)
|
||||
return '';
|
||||
|
||||
const fromStart = 1;
|
||||
const fromEnd = from.length;
|
||||
const fromLen = fromEnd - fromStart;
|
||||
const toStart = 1;
|
||||
const toLen = to.length - toStart;
|
||||
|
||||
// Compare paths to find the longest common path from root
|
||||
const length = (fromLen < toLen ? fromLen : toLen);
|
||||
let lastCommonSep = -1;
|
||||
let i = 0;
|
||||
for (; i < length; i++) {
|
||||
const fromCode = from.charCodeAt(fromStart + i);
|
||||
if (fromCode !== to.charCodeAt(toStart + i))
|
||||
break;
|
||||
else if (fromCode === CHAR_FORWARD_SLASH)
|
||||
lastCommonSep = i;
|
||||
}
|
||||
if (i === length) {
|
||||
if (toLen > length) {
|
||||
if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
|
||||
// We get here if `from` is the exact base path for `to`.
|
||||
// For example: from='/foo/bar'; to='/foo/bar/baz'
|
||||
return to.slice(toStart + i + 1);
|
||||
}
|
||||
if (i === 0) {
|
||||
// We get here if `from` is the root
|
||||
// For example: from='/'; to='/foo'
|
||||
return to.slice(toStart + i);
|
||||
}
|
||||
} else if (fromLen > length) {
|
||||
if (from.charCodeAt(fromStart + i) ===
|
||||
CHAR_FORWARD_SLASH) {
|
||||
// We get here if `to` is the exact base path for `from`.
|
||||
// For example: from='/foo/bar/baz'; to='/foo/bar'
|
||||
lastCommonSep = i;
|
||||
} else if (i === 0) {
|
||||
// We get here if `to` is the root.
|
||||
// For example: from='/foo/bar'; to='/'
|
||||
lastCommonSep = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let out = '';
|
||||
// Generate the relative path based on the path difference between `to`
|
||||
// and `from`.
|
||||
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
|
||||
if (i === fromEnd ||
|
||||
from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
|
||||
out += out.length === 0 ? '..' : '/..';
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, append the rest of the destination (`to`) path that comes after
|
||||
// the common path parts.
|
||||
return `${out}${to.slice(toStart + lastCommonSep)}`;
|
||||
},
|
||||
|
||||
toNamespacedPath(path) {
|
||||
// Non-op on posix systems
|
||||
return path;
|
||||
},
|
||||
|
||||
dirname(path) {
|
||||
if (path.length === 0)
|
||||
return '.';
|
||||
const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH;
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
for (let i = path.length - 1; i >= 1; --i) {
|
||||
if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
|
||||
if (!matchedSlash) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// We saw the first non-path separator
|
||||
matchedSlash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (end === -1)
|
||||
return hasRoot ? '/' : '.';
|
||||
if (hasRoot && end === 1)
|
||||
return '//';
|
||||
return path.slice(0, end);
|
||||
},
|
||||
|
||||
basename(path, ext) {
|
||||
let start = 0;
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
|
||||
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
|
||||
if (ext === path)
|
||||
return '';
|
||||
let extIdx = ext.length - 1;
|
||||
let firstNonSlashEnd = -1;
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
const code = path.charCodeAt(i);
|
||||
if (code === CHAR_FORWARD_SLASH) {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (firstNonSlashEnd === -1) {
|
||||
// We saw the first non-path separator, remember this index in case
|
||||
// we need it if the extension ends up not matching
|
||||
matchedSlash = false;
|
||||
firstNonSlashEnd = i + 1;
|
||||
}
|
||||
if (extIdx >= 0) {
|
||||
// Try to match the explicit extension
|
||||
if (code === ext.charCodeAt(extIdx)) {
|
||||
if (--extIdx === -1) {
|
||||
// We matched the extension, so mark this as the end of our path
|
||||
// component
|
||||
end = i;
|
||||
}
|
||||
} else {
|
||||
// Extension does not match, so our result is the entire path
|
||||
// component
|
||||
extIdx = -1;
|
||||
end = firstNonSlashEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start === end)
|
||||
end = firstNonSlashEnd;
|
||||
else if (end === -1)
|
||||
end = path.length;
|
||||
return path.slice(start, end);
|
||||
}
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
} else if (end === -1) {
|
||||
// We saw the first non-path separator, mark this as the end of our
|
||||
// path component
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (end === -1)
|
||||
return '';
|
||||
return path.slice(start, end);
|
||||
},
|
||||
|
||||
extname(path) {
|
||||
let startDot = -1;
|
||||
let startPart = 0;
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
// Track the state of characters (if any) we see before our first dot and
|
||||
// after any path separator we find
|
||||
let preDotState = 0;
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
const code = path.charCodeAt(i);
|
||||
if (code === CHAR_FORWARD_SLASH) {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
startPart = i + 1;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (end === -1) {
|
||||
// We saw the first non-path separator, mark this as the end of our
|
||||
// extension
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
if (code === CHAR_DOT) {
|
||||
// If this is our first dot, mark it as the start of our extension
|
||||
if (startDot === -1)
|
||||
startDot = i;
|
||||
else if (preDotState !== 1)
|
||||
preDotState = 1;
|
||||
} else if (startDot !== -1) {
|
||||
// We saw a non-dot and non-path separator before our dot, so we should
|
||||
// have a good chance at having a non-empty extension
|
||||
preDotState = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (startDot === -1 ||
|
||||
end === -1 ||
|
||||
// We saw a non-dot character immediately before the dot
|
||||
preDotState === 0 ||
|
||||
// The (right-most) trimmed path component is exactly '..'
|
||||
(preDotState === 1 &&
|
||||
startDot === end - 1 &&
|
||||
startDot === startPart + 1)) {
|
||||
return '';
|
||||
}
|
||||
return path.slice(startDot, end);
|
||||
},
|
||||
|
||||
format: _format.bind( null, '/'),
|
||||
|
||||
parse(path) {
|
||||
const ret = { root: '', dir: '', base: '', ext: '', name: '' };
|
||||
if (path.length === 0)
|
||||
return ret;
|
||||
const isAbsolute =
|
||||
path.charCodeAt(0) === CHAR_FORWARD_SLASH;
|
||||
let start;
|
||||
if (isAbsolute) {
|
||||
ret.root = '/';
|
||||
start = 1;
|
||||
} else {
|
||||
start = 0;
|
||||
}
|
||||
let startDot = -1;
|
||||
let startPart = 0;
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
let i = path.length - 1;
|
||||
|
||||
// Track the state of characters (if any) we see before our first dot and
|
||||
// after any path separator we find
|
||||
let preDotState = 0;
|
||||
|
||||
// Get non-dir info
|
||||
for (; i >= start; --i) {
|
||||
const code = path.charCodeAt(i);
|
||||
if (code === CHAR_FORWARD_SLASH) {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
startPart = i + 1;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (end === -1) {
|
||||
// We saw the first non-path separator, mark this as the end of our
|
||||
// extension
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
if (code === CHAR_DOT) {
|
||||
// If this is our first dot, mark it as the start of our extension
|
||||
if (startDot === -1)
|
||||
startDot = i;
|
||||
else if (preDotState !== 1)
|
||||
preDotState = 1;
|
||||
} else if (startDot !== -1) {
|
||||
// We saw a non-dot and non-path separator before our dot, so we should
|
||||
// have a good chance at having a non-empty extension
|
||||
preDotState = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (end !== -1) {
|
||||
const start = startPart === 0 && isAbsolute ? 1 : startPart;
|
||||
if (startDot === -1 ||
|
||||
// We saw a non-dot character immediately before the dot
|
||||
preDotState === 0 ||
|
||||
// The (right-most) trimmed path component is exactly '..'
|
||||
(preDotState === 1 &&
|
||||
startDot === end - 1 &&
|
||||
startDot === startPart + 1)) {
|
||||
ret.base = ret.name = path.slice(start, end);
|
||||
} else {
|
||||
ret.name = path.slice(start, startDot);
|
||||
ret.base = path.slice(start, end);
|
||||
ret.ext = path.slice(startDot, end);
|
||||
}
|
||||
}
|
||||
|
||||
if (startPart > 0)
|
||||
ret.dir = path.slice(0, startPart - 1);
|
||||
else if (isAbsolute)
|
||||
ret.dir = '/';
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
sep: '/',
|
||||
delimiter: ':',
|
||||
win32: null,
|
||||
posix: null
|
||||
};
|
||||
|
||||
function _format(sep, pathObject) {
|
||||
validateObject(pathObject, 'pathObject');
|
||||
const dir = pathObject.dir || pathObject.root;
|
||||
const base = pathObject.base ||
|
||||
`${pathObject.name || ''}${pathObject.ext || ''}`;
|
||||
if (!dir) {
|
||||
return base;
|
||||
}
|
||||
return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
|
||||
}
|
||||
|
||||
export default path
|
7
packages/puter-dot-js/src/lib/socket.io/socket.io.esm.min.js
vendored
Normal file
7
packages/puter-dot-js/src/lib/socket.io/socket.io.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4385
packages/puter-dot-js/src/lib/socket.io/socket.io.js
Normal file
4385
packages/puter-dot-js/src/lib/socket.io/socket.io.js
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/puter-dot-js/src/lib/socket.io/socket.io.js.map
Normal file
1
packages/puter-dot-js/src/lib/socket.io/socket.io.js.map
Normal file
File diff suppressed because one or more lines are too long
7
packages/puter-dot-js/src/lib/socket.io/socket.io.min.js
vendored
Normal file
7
packages/puter-dot-js/src/lib/socket.io/socket.io.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7
packages/puter-dot-js/src/lib/socket.io/socket.io.msgpack.min.js
vendored
Normal file
7
packages/puter-dot-js/src/lib/socket.io/socket.io.msgpack.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
436
packages/puter-dot-js/src/lib/utils.js
Normal file
436
packages/puter-dot-js/src/lib/utils.js
Normal file
@ -0,0 +1,436 @@
|
||||
/**
|
||||
* Parses a given response text into a JSON object. If the parsing fails due to invalid JSON format,
|
||||
* the original response text is returned.
|
||||
*
|
||||
* @param {string} responseText - The response text to be parsed into JSON. It is expected to be a valid JSON string.
|
||||
* @returns {Object|string} The parsed JSON object if the responseText is valid JSON, otherwise returns the original responseText.
|
||||
* @example
|
||||
* // returns { key: "value" }
|
||||
* parseResponse('{"key": "value"}');
|
||||
*
|
||||
* @example
|
||||
* // returns "Invalid JSON"
|
||||
* parseResponse('Invalid JSON');
|
||||
*/
|
||||
async function parseResponse(target) {
|
||||
if ( target.responseType === 'blob' ) {
|
||||
// Get content type of the blob
|
||||
const contentType = target.getResponseHeader('content-type');
|
||||
if ( contentType.startsWith('application/json') ) {
|
||||
// If the blob is JSON, parse it
|
||||
const text = await target.response.text();
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
return text;
|
||||
}
|
||||
}else if ( contentType.startsWith('application/octet-stream') ) {
|
||||
// If the blob is an octet stream, return the blob
|
||||
return target.response;
|
||||
}
|
||||
|
||||
// Otherwise return an ojbect
|
||||
return {
|
||||
success: true,
|
||||
result: target.response,
|
||||
};
|
||||
}
|
||||
const responseText = target.responseText;
|
||||
try {
|
||||
return JSON.parse(responseText);
|
||||
} catch (error) {
|
||||
return responseText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that generates a UUID (Universally Unique Identifier) using the version 4 format,
|
||||
* which are random UUIDs. It uses the cryptographic number generator available in modern browsers.
|
||||
*
|
||||
* The generated UUID is a 36 character string (32 alphanumeric characters separated by 4 hyphens).
|
||||
* It follows the pattern: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where x is any hexadecimal digit
|
||||
* and y is one of 8, 9, A, or B.
|
||||
*
|
||||
* @returns {string} Returns a new UUID v4 string.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* let id = this.#uuidv4(); // Generate a new UUID
|
||||
*
|
||||
*/
|
||||
function uuidv4(){
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes and returns an XMLHttpRequest object configured for a specific API endpoint, method, and headers.
|
||||
*
|
||||
* @param {string} endpoint - The API endpoint to which the request will be sent. This is appended to the API origin URL.
|
||||
* @param {string} APIOrigin - The origin URL of the API. This is prepended to the endpoint.
|
||||
* @param {string} authToken - The authorization token used for accessing the API. This is included in the request headers.
|
||||
* @param {string} [method='post'] - The HTTP method to be used for the request. Defaults to 'post' if not specified.
|
||||
* @param {string} [contentType='application/json;charset=UTF-8'] - The content type of the request. Defaults to
|
||||
* 'application/json;charset=UTF-8' if not specified.
|
||||
*
|
||||
* @returns {XMLHttpRequest} The initialized XMLHttpRequest object.
|
||||
*/
|
||||
function initXhr(endpoint, APIOrigin, authToken, method= "post", contentType = "application/json;charset=UTF-8", responseType = undefined) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, APIOrigin + endpoint, true);
|
||||
xhr.setRequestHeader("Authorization", "Bearer " + authToken);
|
||||
xhr.setRequestHeader("Content-Type", contentType);
|
||||
xhr.responseType = responseType;
|
||||
return xhr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an HTTP response by invoking appropriate callback functions and resolving or rejecting a promise.
|
||||
*
|
||||
* @param {Function} success_cb - An optional callback function for successful responses. It should take a response object
|
||||
* as its only argument.
|
||||
* @param {Function} error_cb - An optional callback function for error handling. It should take an error object
|
||||
* as its only argument.
|
||||
* @param {Function} resolve_func - A function used to resolve a promise. It should take a response object
|
||||
* as its only argument.
|
||||
* @param {Function} reject_func - A function used to reject a promise. It should take an error object
|
||||
* as its only argument.
|
||||
* @param {Object} response - The HTTP response object from the request. Expected to have 'status' and 'responseText'
|
||||
* properties.
|
||||
*
|
||||
* @returns {void} The function does not return a value but will either resolve or reject a promise based on the
|
||||
* response status.
|
||||
*/
|
||||
async function handle_resp(success_cb, error_cb, resolve_func, reject_func, response){
|
||||
const resp = await parseResponse(response);
|
||||
// error - unauthorized
|
||||
if(response.status === 401){
|
||||
// if error callback is provided, call it
|
||||
if(error_cb && typeof error_cb === 'function')
|
||||
error_cb({status: 401, message: 'Unauthorized'})
|
||||
// reject promise
|
||||
return reject_func({status: 401, message: 'Unauthorized'})
|
||||
}
|
||||
// error - other
|
||||
else if(response.status !== 200){
|
||||
// if error callback is provided, call it
|
||||
if(error_cb && typeof error_cb === 'function')
|
||||
error_cb(resp)
|
||||
// reject promise
|
||||
return reject_func(resp)
|
||||
}
|
||||
// success
|
||||
else{
|
||||
// This is a driver error
|
||||
if(resp.success === false && resp.error?.code === 'permission_denied'){
|
||||
let perm = await puter.ui.requestPermission({permission: 'driver:puter-image-generation:generate'});
|
||||
// try sending again if permission was granted
|
||||
if(perm.granted){
|
||||
// todo repeat request
|
||||
}
|
||||
}
|
||||
// if success callback is provided, call it
|
||||
if(success_cb && typeof success_cb === 'function')
|
||||
success_cb(resp);
|
||||
// resolve with success
|
||||
return resolve_func(resp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error by invoking a specified error callback and then rejecting a promise.
|
||||
*
|
||||
* @param {Function} error_cb - An optional callback function that is called if it's provided.
|
||||
* This function should take an error object as its only argument.
|
||||
* @param {Function} reject_func - A function used to reject a promise. It should take an error object
|
||||
* as its only argument.
|
||||
* @param {Object} error - The error object that is passed to both the error callback and the reject function.
|
||||
*
|
||||
* @returns {void} The function does not return a value but will call the reject function with the error.
|
||||
*/
|
||||
function handle_error(error_cb, reject_func, error){
|
||||
// if error callback is provided, call it
|
||||
if(error_cb && typeof error_cb === 'function')
|
||||
error_cb(error)
|
||||
// reject promise
|
||||
return reject_func(error)
|
||||
}
|
||||
|
||||
function setupXhrEventHandlers(xhr, success_cb, error_cb, resolve_func, reject_func) {
|
||||
// load: success or error
|
||||
xhr.addEventListener('load', function(e){
|
||||
return handle_resp(success_cb, error_cb, resolve_func, reject_func, this, xhr);
|
||||
});
|
||||
|
||||
// error
|
||||
xhr.addEventListener('error', function(e){
|
||||
return handle_error(error_cb, reject_func, this);
|
||||
})
|
||||
}
|
||||
|
||||
const NOOP = () => {};
|
||||
class Valid {
|
||||
static callback (cb) {
|
||||
return (cb && typeof cb === 'function') ? cb : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the hybrid promise/callback function for a particular driver method
|
||||
* @param {string[]} arg_defs - argument names (for now; definitions eventually)
|
||||
* @param {string} driverInterface - name of the interface
|
||||
* @param {string} driverMethod - name of the method
|
||||
*/
|
||||
function make_driver_method(arg_defs, driverInterface, driverMethod, settings = {}) {
|
||||
return async function (...args) {
|
||||
let driverArgs = {};
|
||||
let options = {};
|
||||
|
||||
// Check if the first argument is an object and use it as named parameters
|
||||
if (args.length === 1 && typeof args[0] === 'object' && !Array.isArray(args[0])) {
|
||||
driverArgs = { ...args[0] };
|
||||
options = {
|
||||
success: driverArgs.success,
|
||||
error: driverArgs.error,
|
||||
};
|
||||
// Remove callback functions from driverArgs if they exist
|
||||
delete driverArgs.success;
|
||||
delete driverArgs.error;
|
||||
} else {
|
||||
// Handle as individual arguments
|
||||
arg_defs.forEach((argName, index) => {
|
||||
driverArgs[argName] = args[index];
|
||||
});
|
||||
options = {
|
||||
success: args[arg_defs.length],
|
||||
error: args[arg_defs.length + 1],
|
||||
};
|
||||
}
|
||||
|
||||
// preprocess
|
||||
if(settings.preprocess && typeof settings.preprocess === 'function'){
|
||||
driverArgs = settings.preprocess(driverArgs);
|
||||
}
|
||||
|
||||
return await driverCall(options, driverInterface, driverMethod, driverArgs, settings);
|
||||
};
|
||||
}
|
||||
|
||||
async function driverCall (options, driverInterface, driverMethod, driverArgs, settings) {
|
||||
const tp = new TeePromise();
|
||||
|
||||
driverCall_(
|
||||
options,
|
||||
tp.resolve.bind(tp),
|
||||
tp.reject.bind(tp),
|
||||
driverInterface,
|
||||
driverMethod,
|
||||
driverArgs,
|
||||
undefined,
|
||||
undefined,
|
||||
settings,
|
||||
);
|
||||
|
||||
return await tp;
|
||||
}
|
||||
|
||||
// This function encapsulates the logic for sending a driver call request
|
||||
async function driverCall_(
|
||||
options = {},
|
||||
resolve_func, reject_func,
|
||||
driverInterface, driverMethod, driverArgs,
|
||||
method,
|
||||
contentType = 'application/json;charset=UTF-8',
|
||||
settings = {},
|
||||
) {
|
||||
// If there is no authToken and the environment is web, try authenticating with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
return reject_func({
|
||||
error: {
|
||||
code: 'auth_canceled', message: 'Authentication canceled'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const success_cb = Valid.callback(options.success) ?? NOOP;
|
||||
const error_cb = Valid.callback(options.error) ?? NOOP;
|
||||
// create xhr object
|
||||
const xhr = initXhr('/drivers/call', puter.APIOrigin, puter.authToken, 'POST', contentType);
|
||||
|
||||
if ( settings.responseType ) {
|
||||
xhr.responseType = settings.responseType;
|
||||
}
|
||||
|
||||
// load: success or error
|
||||
xhr.addEventListener('load', async function(response){
|
||||
const resp = await parseResponse(response.target);
|
||||
// HTTP Error - unauthorized
|
||||
if(response.status === 401 || resp?.code === "token_auth_failed"){
|
||||
if(resp?.code === "token_auth_failed" && puter.env === 'web'){
|
||||
try{
|
||||
puter.resetAuthToken();
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
return reject_func({
|
||||
error: {
|
||||
code: 'auth_canceled', message: 'Authentication canceled'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// if error callback is provided, call it
|
||||
if(error_cb && typeof error_cb === 'function')
|
||||
error_cb({status: 401, message: 'Unauthorized'})
|
||||
// reject promise
|
||||
return reject_func({status: 401, message: 'Unauthorized'})
|
||||
}
|
||||
// HTTP Error - other
|
||||
else if(response.status && response.status !== 200){
|
||||
// if error callback is provided, call it
|
||||
error_cb(resp)
|
||||
// reject promise
|
||||
return reject_func(resp)
|
||||
}
|
||||
// HTTP Success
|
||||
else{
|
||||
// Driver Error: permission denied
|
||||
if(resp.success === false && resp.error?.code === 'permission_denied'){
|
||||
let perm = await puter.ui.requestPermission({permission: 'driver:' + driverInterface + ':' + driverMethod});
|
||||
// try sending again if permission was granted
|
||||
if(perm.granted){
|
||||
// repeat request with permission granted
|
||||
return driverCall_(options, resolve_func, reject_func, driverInterface, driverMethod, driverArgs, method, contentType, settings);
|
||||
}else{
|
||||
// if error callback is provided, call it
|
||||
error_cb(resp)
|
||||
// reject promise
|
||||
return reject_func(resp)
|
||||
}
|
||||
}
|
||||
// Driver Error: other
|
||||
else if(resp.success === false){
|
||||
// if error callback is provided, call it
|
||||
error_cb(resp)
|
||||
// reject promise
|
||||
return reject_func(resp)
|
||||
}
|
||||
|
||||
let result = resp.result !== undefined ? resp.result : resp;
|
||||
if ( settings.transform ) {
|
||||
result = await settings.transform(result);
|
||||
}
|
||||
|
||||
// Success: if callback is provided, call it
|
||||
if(resolve_func.success)
|
||||
success_cb(result);
|
||||
// Success: resolve with the result
|
||||
return resolve_func(result);
|
||||
}
|
||||
});
|
||||
|
||||
// error
|
||||
xhr.addEventListener('error', function(e){
|
||||
return handle_error(error_cb, reject_func, this);
|
||||
})
|
||||
|
||||
// send request
|
||||
xhr.send(JSON.stringify({
|
||||
interface: driverInterface,
|
||||
test_mode: settings?.test_mode,
|
||||
method: driverMethod,
|
||||
args: driverArgs,
|
||||
}));
|
||||
}
|
||||
|
||||
class TeePromise {
|
||||
static STATUS_PENDING = {};
|
||||
static STATUS_RUNNING = {};
|
||||
static STATUS_DONE = {};
|
||||
constructor () {
|
||||
this.status_ = this.constructor.STATUS_PENDING;
|
||||
this.donePromise = new Promise((resolve, reject) => {
|
||||
this.doneResolve = resolve;
|
||||
this.doneReject = reject;
|
||||
});
|
||||
}
|
||||
get status () {
|
||||
return this.status_;
|
||||
}
|
||||
set status (status) {
|
||||
this.status_ = status;
|
||||
if ( status === this.constructor.STATUS_DONE ) {
|
||||
this.doneResolve();
|
||||
}
|
||||
}
|
||||
resolve (value) {
|
||||
this.status_ = this.constructor.STATUS_DONE;
|
||||
this.doneResolve(value);
|
||||
}
|
||||
awaitDone () {
|
||||
return this.donePromise;
|
||||
}
|
||||
then (fn, rfn) {
|
||||
return this.donePromise.then(fn, rfn);
|
||||
}
|
||||
|
||||
reject (err) {
|
||||
this.status_ = this.constructor.STATUS_DONE;
|
||||
this.doneReject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use then() instead
|
||||
*/
|
||||
onComplete(fn) {
|
||||
return this.then(fn);
|
||||
}
|
||||
}
|
||||
|
||||
async function blob_to_url (blob) {
|
||||
const tp = new TeePromise();
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => tp.resolve(reader.result);
|
||||
reader.readAsDataURL(blob);
|
||||
return await tp;
|
||||
}
|
||||
|
||||
function blobToDataUri(blob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
reader.onerror = function(error) {
|
||||
reject(error);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function arrayBufferToDataUri(arrayBuffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
reader.onerror = function(error) {
|
||||
reject(error);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
export {parseResponse, uuidv4, handle_resp, handle_error, initXhr, setupXhrEventHandlers, driverCall,
|
||||
TeePromise,
|
||||
make_driver_method,
|
||||
blob_to_url,
|
||||
arrayBufferToDataUri,
|
||||
blobToDataUri,
|
||||
};
|
253
packages/puter-dot-js/src/modules/AI.js
Normal file
253
packages/puter-dot-js/src/modules/AI.js
Normal file
@ -0,0 +1,253 @@
|
||||
import * as utils from '../lib/utils.js'
|
||||
|
||||
class AI{
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token and resets the socket connection with the updated token, if applicable.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [AI]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [AI]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
img2txt = async (...args) => {
|
||||
let MAX_INPUT_SIZE = 10 * 1024 * 1024;
|
||||
let options = {};
|
||||
let testMode = false;
|
||||
|
||||
// Check that the argument is not undefined or null
|
||||
if(!args){
|
||||
throw({message: 'Arguments are required', code: 'arguments_required'});
|
||||
}
|
||||
|
||||
// if argument is string transform it to the object that the API expects
|
||||
if (typeof args[0] === 'string' || args[0] instanceof Blob) {
|
||||
options.source = args[0];
|
||||
}
|
||||
|
||||
// if input is a blob, transform it to a data URI
|
||||
if (args[0].source instanceof Blob) {
|
||||
options.source = await utils.blobToDataUri(args[0].source);
|
||||
}
|
||||
|
||||
// check input size
|
||||
if (options.source.length > this.MAX_INPUT_SIZE) {
|
||||
throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
|
||||
}
|
||||
|
||||
// determine if test mode is enabled
|
||||
if (typeof args[1] === 'boolean' && args[1] === true ||
|
||||
typeof args[2] === 'boolean' && args[2] === true ||
|
||||
typeof args[3] === 'boolean' && args[3] === true) {
|
||||
testMode = true;
|
||||
}
|
||||
|
||||
console.log(args, options);
|
||||
return await utils.make_driver_method(['source'], 'puter-ocr', 'recognize', {
|
||||
test_mode: testMode ?? false,
|
||||
transform: async (result) => {
|
||||
let str = '';
|
||||
for (let i = 0; i < result?.blocks?.length; i++) {
|
||||
if("text/textract:LINE" === result.blocks[i].type)
|
||||
str += result.blocks[i].text + "\n";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}).call(this, options);
|
||||
}
|
||||
|
||||
txt2speech = async (...args) => {
|
||||
let MAX_INPUT_SIZE = 3000;
|
||||
let options = {};
|
||||
let testMode = false;
|
||||
|
||||
if(!args){
|
||||
throw({message: 'Arguments are required', code: 'arguments_required'});
|
||||
}
|
||||
|
||||
// if argument is string transform it to the object that the API expects
|
||||
if (typeof args[0] === 'string') {
|
||||
options = { text: args[0] };
|
||||
}
|
||||
|
||||
// if second argument is string, it's the language
|
||||
if (args[1] && typeof args[1] === 'string') {
|
||||
options.language = args[1];
|
||||
}
|
||||
|
||||
// check input size
|
||||
if (options.text.length > this.MAX_INPUT_SIZE) {
|
||||
throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
|
||||
}
|
||||
|
||||
// determine if test mode is enabled
|
||||
if (typeof args[1] === 'boolean' && args[1] === true ||
|
||||
typeof args[2] === 'boolean' && args[2] === true ||
|
||||
typeof args[3] === 'boolean' && args[3] === true) {
|
||||
testMode = true;
|
||||
}
|
||||
|
||||
return await utils.make_driver_method(['source'], 'puter-tts', 'synthesize', {
|
||||
responseType: 'blob',
|
||||
test_mode: testMode ?? false,
|
||||
transform: async (result) => {
|
||||
const url = await utils.blob_to_url(result);
|
||||
const audio = new Audio(url);
|
||||
audio.toString = () => url;
|
||||
audio.valueOf = () => url;
|
||||
return audio;
|
||||
}
|
||||
}).call(this, options);
|
||||
}
|
||||
|
||||
|
||||
// accepts either a string or an array of message objects
|
||||
// if string, it's treated as the prompt which is a shorthand for { messages: [{ content: prompt }] }
|
||||
// if object, it's treated as the full argument object that the API expects
|
||||
chat = async (...args) => {
|
||||
let options = {};
|
||||
let testMode = false;
|
||||
|
||||
// Check that the argument is not undefined or null
|
||||
if(!args){
|
||||
throw({message: 'Arguments are required', code: 'arguments_required'});
|
||||
}
|
||||
|
||||
// ai.chat(prompt)
|
||||
// ai.chat(prompt, testMode)
|
||||
if (typeof args[0] === 'string' && (!args[1] || typeof args[1] === 'boolean')) {
|
||||
options = { messages: [{ content: args[0] }] };
|
||||
}
|
||||
// ai.chat(prompt, imageURL/File)
|
||||
// ai.chat(prompt, imageURL/File, testMode)
|
||||
else if (typeof args[0] === 'string' && (typeof args[1] === 'string' || args[1] instanceof File)) {
|
||||
// if imageURL is a File, transform it to a data URI
|
||||
if(args[1] instanceof File){
|
||||
args[1] = await utils.blobToDataUri(args[1]);
|
||||
}
|
||||
|
||||
// parse args[1] as an image_url object
|
||||
options = {
|
||||
vision: true,
|
||||
messages: [
|
||||
{
|
||||
content: [
|
||||
args[0],
|
||||
{
|
||||
image_url: {
|
||||
url: args[1]
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
// chat(prompt, [imageURLs])
|
||||
else if (typeof args[0] === 'string' && Array.isArray(args[1])) {
|
||||
// parse args[1] as an array of image_url objects
|
||||
for (let i = 0; i < args[1].length; i++) {
|
||||
args[1][i] = { image_url: { url: args[1][i] } };
|
||||
}
|
||||
options = {
|
||||
vision: true,
|
||||
messages: [
|
||||
{
|
||||
content: [
|
||||
args[0],
|
||||
...args[1]
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
// chat([messages])
|
||||
else if (Array.isArray(args[0])) {
|
||||
options = { messages: args[0] };
|
||||
}
|
||||
|
||||
// determine if testMode is enabled
|
||||
if (typeof args[1] === 'boolean' && args[1] === true ||
|
||||
typeof args[2] === 'boolean' && args[2] === true ||
|
||||
typeof args[3] === 'boolean' && args[3] === true) {
|
||||
testMode = true;
|
||||
}
|
||||
|
||||
// Call the original chat.complete method
|
||||
return await utils.make_driver_method(['messages'], 'puter-chat-completion', 'complete', {
|
||||
test_mode: testMode ?? false,
|
||||
transform: async (result) => {
|
||||
result.toString = () => {
|
||||
return result.message?.content;
|
||||
};
|
||||
|
||||
result.valueOf = () => {
|
||||
return result.message?.content;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}).call(this, options);
|
||||
}
|
||||
|
||||
txt2img = async (...args) => {
|
||||
let options = {};
|
||||
let testMode = false;
|
||||
|
||||
if(!args){
|
||||
throw({message: 'Arguments are required', code: 'arguments_required'});
|
||||
}
|
||||
|
||||
// if argument is string transform it to the object that the API expects
|
||||
if (typeof args[0] === 'string') {
|
||||
options = { prompt: args[0] };
|
||||
}
|
||||
|
||||
// if second argument is string, it's the `testMode`
|
||||
if (typeof args[1] === 'boolean' && args[1] === true) {
|
||||
testMode = true;
|
||||
}
|
||||
|
||||
// Call the original chat.complete method
|
||||
return await utils.make_driver_method(['prompt'], 'puter-image-generation', 'generate', {
|
||||
responseType: 'blob',
|
||||
test_mode: testMode ?? false,
|
||||
transform: async blob => {
|
||||
let img = new Image();
|
||||
img.src = await utils.blob_to_url(blob);
|
||||
img.toString = () => img.src;
|
||||
img.valueOf = () => img.src;
|
||||
return img;
|
||||
}
|
||||
}).call(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
export default AI;
|
158
packages/puter-dot-js/src/modules/Apps.js
Normal file
158
packages/puter-dot-js/src/modules/Apps.js
Normal file
@ -0,0 +1,158 @@
|
||||
import * as utils from '../lib/utils.js'
|
||||
|
||||
class Apps{
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [Apps]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [Apps]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
list = async (...args) => {
|
||||
let options = {};
|
||||
|
||||
options = { "predicate": ['user-can-edit'] };
|
||||
|
||||
return utils.make_driver_method(['uid'], 'puter-apps', 'select').call(this, options);
|
||||
}
|
||||
|
||||
create = async (...args) => {
|
||||
let options = {};
|
||||
// * allows for: puter.apps.new('example-app') *
|
||||
if (typeof args[0] === 'string') {
|
||||
let indexURL = args[1];
|
||||
let title = args[2] ?? args[0];
|
||||
|
||||
options = { object: { name: args[0], index_url: indexURL, title: title}};
|
||||
}else if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
let options_raw = args[0];
|
||||
options = {
|
||||
object: {
|
||||
name: options_raw.name,
|
||||
index_url: options_raw.indexURL,
|
||||
title: options_raw.title,
|
||||
description: options_raw.description,
|
||||
icon: options_raw.icon,
|
||||
maximize_on_start: options_raw.maximizeOnStart,
|
||||
filetype_associations: options_raw.filetypeAssociations,
|
||||
}
|
||||
};
|
||||
}
|
||||
// Call the original chat.complete method
|
||||
return await utils.make_driver_method(['object'], 'puter-apps', 'create').call(this, options);
|
||||
}
|
||||
|
||||
update = async(...args) => {
|
||||
let options = {};
|
||||
|
||||
// if there is one string argument, assume it is the app name
|
||||
// * allows for: puter.apps.update('example-app') *
|
||||
if (Array.isArray(args) && typeof args[0] === 'string') {
|
||||
let object_raw = args[1];
|
||||
let object = {
|
||||
name: object_raw.name,
|
||||
index_url: object_raw.indexURL,
|
||||
title: object_raw.title,
|
||||
description: object_raw.description,
|
||||
icon: object_raw.icon,
|
||||
maximize_on_start: object_raw.maximizeOnStart,
|
||||
filetype_associations: object_raw.filetypeAssociations,
|
||||
};
|
||||
|
||||
options = { id: { name: args[0]}, object: object};
|
||||
}
|
||||
|
||||
// Call the original chat.complete method
|
||||
return await utils.make_driver_method(['object'], 'puter-apps', 'update').call(this, options);
|
||||
}
|
||||
|
||||
get = async(...args) => {
|
||||
let options = {};
|
||||
// if there is one string argument, assume it is the app name
|
||||
// * allows for: puter.apps.get('example-app') *
|
||||
if (Array.isArray(args) && typeof args[0] === 'string') {
|
||||
options = { id: {name: args[0]}};
|
||||
}
|
||||
return utils.make_driver_method(['uid'], 'puter-apps', 'read').call(this, options);
|
||||
}
|
||||
|
||||
delete = async(...args) => {
|
||||
let options = {};
|
||||
// if there is one string argument, assume it is the app name
|
||||
// * allows for: puter.apps.get('example-app') *
|
||||
if (Array.isArray(args) && typeof args[0] === 'string') {
|
||||
options = { id: {name: args[0]}};
|
||||
}
|
||||
return utils.make_driver_method(['uid'], 'puter-apps', 'delete').call(this, options);
|
||||
}
|
||||
|
||||
getDeveloperProfile = function(...args){
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = utils.initXhr('/get-dev-profile', puter.APIOrigin, puter.authToken, 'get');
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Apps;
|
115
packages/puter-dot-js/src/modules/Auth.js
Normal file
115
packages/puter-dot-js/src/modules/Auth.js
Normal file
@ -0,0 +1,115 @@
|
||||
import * as utils from '../lib/utils.js'
|
||||
|
||||
class Auth{
|
||||
// Used to generate a unique message id for each message sent to the host environment
|
||||
// we start from 1 because 0 is falsy and we want to avoid that for the message id
|
||||
#messageID = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [Auth]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [Auth]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
signIn = () =>{
|
||||
return new Promise((resolve, reject) => {
|
||||
let msg_id = this.#messageID++;
|
||||
|
||||
let w = 600;
|
||||
let h = 600;
|
||||
let title = 'Puter';
|
||||
var left = (screen.width/2)-(w/2);
|
||||
var top = (screen.height/2)-(h/2);
|
||||
window.open(puter.defaultGUIOrigin + '/action/sign-in?embedded_in_popup=true&msg_id=' + msg_id,
|
||||
title,
|
||||
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
|
||||
|
||||
window.addEventListener('message', function(e){
|
||||
if(e.data.msg_id == msg_id){
|
||||
// remove redundant attributes
|
||||
delete e.data.msg_id;
|
||||
delete e.data.msg;
|
||||
|
||||
if(e.data.success){
|
||||
// set the auth token
|
||||
puter.setAuthToken(e.data.token);
|
||||
|
||||
resolve(e.data);
|
||||
}else
|
||||
reject(e.data);
|
||||
|
||||
// delete the listener
|
||||
window.removeEventListener('message', this);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isSignedIn = () =>{
|
||||
if(puter.authToken)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
getUser = function(...args){
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = utils.initXhr('/whoami', puter.APIOrigin, puter.authToken, 'get');
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
|
||||
signOut = () =>{
|
||||
puter.resetAuthToken();
|
||||
}
|
||||
}
|
||||
|
||||
export default Auth
|
98
packages/puter-dot-js/src/modules/FSItem.js
Normal file
98
packages/puter-dot-js/src/modules/FSItem.js
Normal file
@ -0,0 +1,98 @@
|
||||
import path from "../lib/path.js"
|
||||
|
||||
class FSItem{
|
||||
constructor(options){
|
||||
this.readURL = options.readURL ?? options.read_url;
|
||||
this.writeURL = options.writeURL ?? options.write_url;
|
||||
this.metadataURL = options.metadataURL ?? options.metadata_url;
|
||||
this.name = options.name ?? options.fsentry_name;
|
||||
this.uid = options.uid ?? options.uuid ?? options.fsentry_uid ?? options.fsentry_id ?? options.fsentry_uuid ?? options.id;
|
||||
this.id = this.uid;
|
||||
this.uuid = this.uid;
|
||||
this.path = options.path ?? options.fsentry_path;
|
||||
this.size = options.size ?? options.fsentry_size;
|
||||
this.accessed = options.accessed ?? options.fsentry_accessed;
|
||||
this.modified = options.modified ?? options.fsentry_modified;
|
||||
this.created = options.created ?? options.fsentry_created;
|
||||
this.isDirectory = (options.isDirectory || options.is_dir || options.fsentry_is_dir) ? true : false;
|
||||
}
|
||||
|
||||
write = async function(data){
|
||||
return puter.fs.write(
|
||||
this.path,
|
||||
new File([data], this.name),
|
||||
{
|
||||
overwrite: true,
|
||||
dedupeName: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Watches for changes to the item, and calls the callback function
|
||||
// with the new data when a change is detected.
|
||||
watch = function(callback){
|
||||
// todo - implement
|
||||
}
|
||||
|
||||
open = function(callback){
|
||||
// todo - implement
|
||||
}
|
||||
|
||||
// Set wallpaper
|
||||
setAsWallpaper = function(options, callback){
|
||||
// todo - implement
|
||||
}
|
||||
|
||||
rename = function(new_name){
|
||||
return puter.fs.rename(this.uid, new_name);
|
||||
}
|
||||
|
||||
move = function(dest_path, overwrite=false, new_name){
|
||||
return puter.fs.move(this.path, dest_path, overwrite, new_name);
|
||||
}
|
||||
|
||||
copy = function(destination_directory, auto_rename=false, overwrite=false){
|
||||
return puter.fs.copy(this.path, destination_directory, auto_rename, overwrite);
|
||||
}
|
||||
|
||||
delete = function(){
|
||||
return puter.fs.delete(this.path);
|
||||
}
|
||||
|
||||
versions = async function(){
|
||||
// todo - implement
|
||||
}
|
||||
|
||||
trash = function(){
|
||||
// todo make sure puter allows for moving to trash by default
|
||||
// todo implement trashing
|
||||
}
|
||||
|
||||
mkdir = async function(name, auto_rename = false){
|
||||
// Don't proceed if this is not a directory, throw error
|
||||
if(!this.isDirectory)
|
||||
throw new Error('mkdir() can only be called on a directory');
|
||||
|
||||
// mkdir
|
||||
return puter.fs.mkdir(path.join(this.path, name));
|
||||
}
|
||||
|
||||
metadata = async function(){
|
||||
// todo - implement
|
||||
}
|
||||
|
||||
readdir = async function(){
|
||||
// Don't proceed if this is not a directory, throw error
|
||||
if(!this.isDirectory)
|
||||
throw new Error('readdir() can only be called on a directory');
|
||||
|
||||
// readdir
|
||||
return puter.fs.readdir(this.path);
|
||||
}
|
||||
|
||||
read = async function(){
|
||||
return puter.fs.read(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
export default FSItem;
|
133
packages/puter-dot-js/src/modules/FileSystem/index.js
Normal file
133
packages/puter-dot-js/src/modules/FileSystem/index.js
Normal file
@ -0,0 +1,133 @@
|
||||
import io from '../../lib/socket.io/socket.io.esm.min.js';
|
||||
|
||||
// Operations
|
||||
import readdir from "./operations/readdir.js";
|
||||
import stat from "./operations/stat.js";
|
||||
import space from "./operations/space.js";
|
||||
import mkdir from "./operations/mkdir.js";
|
||||
import copy from "./operations/copy.js";
|
||||
import rename from "./operations/rename.js";
|
||||
import upload from "./operations/upload.js";
|
||||
import read from "./operations/read.js";
|
||||
import move from "./operations/move.js";
|
||||
import write from "./operations/write.js";
|
||||
import sign from "./operations/sign.js";
|
||||
// Why is this called deleteFSEntry instead of just delete? because delete is
|
||||
// a reserved keyword in javascript
|
||||
import deleteFSEntry from "./operations/deleteFSEntry.js";
|
||||
|
||||
class FileSystem{
|
||||
|
||||
readdir = readdir;
|
||||
stat = stat;
|
||||
space = space;
|
||||
mkdir = mkdir;
|
||||
copy = copy;
|
||||
rename = rename;
|
||||
upload = upload;
|
||||
read = read;
|
||||
// Why is this called deleteFSEntry instead of just delete? because delete is
|
||||
// a reserved keyword in javascript.
|
||||
delete = deleteFSEntry;
|
||||
move = move;
|
||||
write = write;
|
||||
sign = sign;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
* and connects to the socket.
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
// Connect socket.
|
||||
this.initializeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the socket connection to the server using the current API origin.
|
||||
* If a socket connection already exists, it disconnects it before creating a new one.
|
||||
* Sets up various event listeners on the socket to handle different socket events like
|
||||
* connect, disconnect, reconnect, reconnect_attempt, reconnect_error, reconnect_failed, and error.
|
||||
*
|
||||
* @memberof FileSystem
|
||||
* @returns {void}
|
||||
*/
|
||||
initializeSocket() {
|
||||
if (this.socket) {
|
||||
this.socket.disconnect();
|
||||
}
|
||||
|
||||
this.socket = io(this.APIOrigin, {
|
||||
query: {
|
||||
auth_token: this.authToken,
|
||||
}
|
||||
});
|
||||
|
||||
this.bindSocketEvents();
|
||||
}
|
||||
|
||||
bindSocketEvents() {
|
||||
this.socket.on('connect', () => {
|
||||
console.log('FileSystem Socket: Connected', this.socket.id);
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
console.log('FileSystem Socket: Disconnected');
|
||||
});
|
||||
|
||||
this.socket.on('reconnect', (attempt) => {
|
||||
console.log('FileSystem Socket: Reconnected', this.socket.id);
|
||||
});
|
||||
|
||||
this.socket.on('reconnect_attempt', (attempt) => {
|
||||
console.log('FileSystem Socket: Reconnection Attemps', attempt);
|
||||
});
|
||||
|
||||
this.socket.on('reconnect_error', (error) => {
|
||||
console.log('FileSystem Socket: Reconnection Error', error);
|
||||
});
|
||||
|
||||
this.socket.on('reconnect_failed', () => {
|
||||
console.log('FileSystem Socket: Reconnection Failed');
|
||||
});
|
||||
|
||||
this.socket.on('error', (error) => {
|
||||
console.error('FileSystem Socket Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token and resets the socket connection with the updated token.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [FileSystem]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
// reset socket
|
||||
this.initializeSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin and resets the socket connection with the updated API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [Apps]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
// reset socket
|
||||
this.initializeSocket();
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSystem;
|
@ -0,0 +1,61 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const copy = function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
source: args[0],
|
||||
destination: args[1],
|
||||
overwrite: args[2]?.overwrite,
|
||||
new_name: args[2]?.newName || args[2]?.new_name,
|
||||
create_missing_parents: args[2]?.createMissingParents || args[2]?.create_missing_parents,
|
||||
new_metadata: args[2]?.newMetadata || args[2]?.new_metadata,
|
||||
original_client_socket_id: args[2]?.excludeSocketID || args[2]?.original_client_socket_id,
|
||||
success: args[3],
|
||||
error: args[4],
|
||||
// Add more if needed...
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// convert paths to absolute path
|
||||
options.source = getAbsolutePathForApp(options.source);
|
||||
options.destination = getAbsolutePathForApp(options.destination);
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/copy', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
original_client_socket_id: this.socket.id,
|
||||
socket_id: this.socket.id,
|
||||
source: options.source,
|
||||
destination: options.destination,
|
||||
overwrite: options.overwrite,
|
||||
new_name: (options.new_name || options.newName),
|
||||
// if user is copying an item to where its source is, change the name so there is no conflict
|
||||
dedupe_name: (options.dedupe_name || options.dedupeName),
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
export default copy;
|
@ -0,0 +1,59 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
// why is this called deleteFSEntry instead of just delete?
|
||||
// because delete is a reserved keyword in javascript
|
||||
const deleteFSEntry = async function(...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
}
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
else {
|
||||
options = {
|
||||
paths: args[0],
|
||||
recursive: args[1]?.recursive ?? true,
|
||||
descendantsOnly: args[1]?.descendantsOnly ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
// If paths is a string, convert to array
|
||||
// this is to make it easier for the user to provide a single path without having to wrap it in an array
|
||||
let paths = options.paths;
|
||||
if(typeof paths === 'string')
|
||||
paths = [paths];
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/delete', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
// convert paths to absolute paths
|
||||
paths = paths.map((path) => {
|
||||
return getAbsolutePathForApp(path);
|
||||
})
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
paths: paths,
|
||||
descendants_only: (options.descendants_only || options.descendantsOnly) ?? false,
|
||||
recursive: options.recursive ?? true,
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
export default deleteFSEntry;
|
@ -0,0 +1,59 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
import path from "../../../lib/path.js"
|
||||
|
||||
const mkdir = function (...args) {
|
||||
let options = {};
|
||||
|
||||
// If first argument is a string and the second is an object, or if the first is an object
|
||||
if ((typeof args[0] === 'string' && typeof args[1] === 'object' && !(args[1] instanceof Function)) || (typeof args[0] === 'object' && args[0] !== null)) {
|
||||
// If it's a string followed by an object, it means path then options
|
||||
if (typeof args[0] === 'string') {
|
||||
options.path = args[0];
|
||||
// Merge the options
|
||||
Object.assign(options, args[1]);
|
||||
options.success = args[2];
|
||||
options.error = args[3];
|
||||
} else {
|
||||
options = args[0];
|
||||
}
|
||||
} else if (typeof args[0] === 'string') {
|
||||
// it means it's a path then functions (success and optionally error)
|
||||
options.path = args[0];
|
||||
options.success = args[1];
|
||||
options.error = args[2];
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/mkdir', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
options.path = getAbsolutePathForApp(options.path);
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
parent: path.dirname(options.path),
|
||||
path: path.basename(options.path),
|
||||
overwrite: options.overwrite ?? false,
|
||||
dedupe_name: (options.rename || options.dedupeName) ?? false,
|
||||
shortcut_to: options.shortcutTo,
|
||||
original_client_socket_id: this.socket.id,
|
||||
create_missing_parents: (options.recursive || options.createMissingParents) ?? false,
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
export default mkdir;
|
@ -0,0 +1,57 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const move = function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
source: args[0],
|
||||
destination: args[1],
|
||||
overwrite: args[2]?.overwrite,
|
||||
new_name: args[2]?.newName || args[2]?.new_name,
|
||||
create_missing_parents: args[2]?.createMissingParents || args[2]?.create_missing_parents,
|
||||
new_metadata: args[2]?.newMetadata || args[2]?.new_metadata,
|
||||
original_client_socket_id: args[2]?.excludeSocketID || args[2]?.original_client_socket_id,
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// convert source and destination to absolute path
|
||||
options.source = getAbsolutePathForApp(options.source);
|
||||
options.destination = getAbsolutePathForApp(options.destination);
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/move', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
source: options.source,
|
||||
destination: options.destination,
|
||||
overwrite: options.overwrite,
|
||||
new_name: (options.new_name || options.newName),
|
||||
create_missing_parents: (options.create_missing_parents || options.createMissingParents),
|
||||
new_metadata: (options.new_metadata || options.newMetadata),
|
||||
original_client_socket_id: options.excludeSocketID,
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
export default move;
|
@ -0,0 +1,44 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const read = function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
path: typeof args[0] === 'string' ? args[0] : (typeof args[0] === 'object' && args[0] !== null ? args[0].path : args[0]),
|
||||
success: args[1],
|
||||
error: args[2],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// convert path to absolute path
|
||||
options.path = getAbsolutePathForApp(options.path);
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/read?file=' + encodeURIComponent(options.path), this.APIOrigin, this.authToken, 'get', "application/json;charset=UTF-8", 'blob');
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
|
||||
export default read;
|
@ -0,0 +1,46 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const readdir = async function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
path: args[0],
|
||||
success: args[1],
|
||||
error: args[2],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// path is required
|
||||
if(!options.path){
|
||||
throw new Error({ code: 'NO_PATH', message: 'No path provided.' });
|
||||
}
|
||||
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/readdir', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send(JSON.stringify({path: getAbsolutePathForApp(options.path)}));
|
||||
})
|
||||
}
|
||||
|
||||
export default readdir;
|
@ -0,0 +1,56 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const rename = function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
path: args[0],
|
||||
new_name: args[1],
|
||||
success: args[2],
|
||||
error: args[3],
|
||||
// Add more if needed...
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/rename', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
let dataToSend = {
|
||||
original_client_socket_id: options.excludeSocketID || options.original_client_socket_id,
|
||||
new_name: options.new_name || options.newName,
|
||||
};
|
||||
|
||||
if (options.uid !== undefined) {
|
||||
dataToSend.uid = options.uid;
|
||||
} else if (options.path !== undefined) {
|
||||
// If dirPath is not provided or it's not starting with a slash, it means it's a relative path
|
||||
// in that case, we need to prepend the app's root directory to it
|
||||
dataToSend.path = getAbsolutePathForApp(options.path);
|
||||
}
|
||||
|
||||
xhr.send(JSON.stringify(dataToSend));
|
||||
})
|
||||
}
|
||||
|
||||
export default rename;
|
103
packages/puter-dot-js/src/modules/FileSystem/operations/sign.js
Normal file
103
packages/puter-dot-js/src/modules/FileSystem/operations/sign.js
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
|
||||
/**
|
||||
* Signs a file system entry or entries and optionally calls a provided callback with the result.
|
||||
* If a single item is passed, it is converted into an array.
|
||||
* Sends a POST request to the server to sign the items.
|
||||
*
|
||||
* @param {(Object|Object[])} items - The file system entry or entries to be signed. Can be a single object or an array of objects.
|
||||
* @param {function} [callback] - Optional callback function to be invoked with the result of the signing.
|
||||
* @returns {(Object|Object[])} If a single item was passed, returns a single object. If multiple items were passed, returns an array of objects.
|
||||
* @throws {Error} If the AJAX request fails.
|
||||
* @async
|
||||
*/
|
||||
const sign = function(...args){
|
||||
let options;
|
||||
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
app_uid: args[0],
|
||||
items: args[1],
|
||||
success: args[2],
|
||||
error: args[3],
|
||||
// Add more if needed...
|
||||
};
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let items = options.items;
|
||||
|
||||
// if only a single item is passed, convert it to array
|
||||
// so that the code below can work with arrays
|
||||
if(!Array.isArray(items)){
|
||||
items = [items]
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/sign', this.APIOrigin, this.authToken);
|
||||
|
||||
// response
|
||||
xhr.addEventListener('load', async function(e){
|
||||
const resp = await utils.parseResponse(this);
|
||||
// error
|
||||
if(this.status !== 200){
|
||||
// if error callback is provided, call it
|
||||
if(options.error && typeof options.error === 'function')
|
||||
options.error(resp)
|
||||
// reject promise
|
||||
return reject(resp)
|
||||
}
|
||||
// success
|
||||
else{
|
||||
let res = resp;
|
||||
let result;
|
||||
let token = res.token;
|
||||
// if only a single item was passed, return a single object
|
||||
if(items.length == 1){
|
||||
result = {...(res.signatures[0])};
|
||||
}
|
||||
// if multiple items were passed, return an array of objects
|
||||
else{
|
||||
let obj=[];
|
||||
for(let i=0; i<res.signatures.length; i++){
|
||||
obj.push({...res.signatures[i]});
|
||||
}
|
||||
result = obj;
|
||||
}
|
||||
|
||||
// if success callback is provided, call it
|
||||
if(options.success && typeof options.success === 'function')
|
||||
options.success({token: token, items: result});
|
||||
// resolve with success
|
||||
return resolve({token: token, items: result});
|
||||
}
|
||||
});
|
||||
|
||||
xhr.upload.addEventListener('progress', function(e){
|
||||
})
|
||||
|
||||
// error
|
||||
xhr.addEventListener('error', function(e){
|
||||
return utils.handle_error(options.error, reject, this);
|
||||
})
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
app_uid: options.app_uid,
|
||||
items: items
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
export default sign;
|
@ -0,0 +1,40 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
|
||||
const space = function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
// Add more if needed...
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/df', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
|
||||
export default space;
|
@ -0,0 +1,57 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const stat = async function (...args) {
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
path: args[0],
|
||||
options: typeof args[1] === 'object' ? args[1] : {},
|
||||
success: typeof args[1] === 'object' ? args[2] : args[1],
|
||||
error: typeof args[1] === 'object' ? args[3] : args[2],
|
||||
// Add more if needed...
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject('Authentication failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// create xhr object
|
||||
const xhr = utils.initXhr('/stat', this.APIOrigin, this.authToken);
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
let dataToSend = {};
|
||||
if (options.uid !== undefined) {
|
||||
dataToSend.uid = options.uid;
|
||||
} else if (options.path !== undefined) {
|
||||
// If dirPath is not provided or it's not starting with a slash, it means it's a relative path
|
||||
// in that case, we need to prepend the app's root directory to it
|
||||
dataToSend.path = getAbsolutePathForApp(options.path);
|
||||
}
|
||||
|
||||
dataToSend.return_subdomains = options.returnSubdomains;
|
||||
dataToSend.return_permissions = options.returnPermissions;
|
||||
dataToSend.return_versions = options.returnVersions;
|
||||
dataToSend.return_size = options.returnSize;
|
||||
|
||||
xhr.send(JSON.stringify(dataToSend));
|
||||
})
|
||||
}
|
||||
|
||||
export default stat;
|
@ -0,0 +1,419 @@
|
||||
import * as utils from '../../../lib/utils.js';
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
import path from "../../../lib/path.js"
|
||||
|
||||
const upload = async function(items, dirPath, options = {}){
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// If auth token is not provided and we are in the web environment,
|
||||
// try to authenticate with Puter
|
||||
if(!puter.authToken && puter.env === 'web'){
|
||||
try{
|
||||
await puter.ui.authenticateWithPuter();
|
||||
}catch(e){
|
||||
// if authentication fails, throw an error
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
// xhr object to be used for the upload
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
// Can not write to root
|
||||
if(dirPath === '/'){
|
||||
// if error callback is provided, call it
|
||||
if(options.error && typeof options.error === 'function')
|
||||
options.error('Can not upload to root directory.');
|
||||
return reject('Can not upload to root directory.');
|
||||
}
|
||||
|
||||
// If dirPath is not provided or it's not starting with a slash, it means it's a relative path
|
||||
// in that case, we need to prepend the app's root directory to it
|
||||
dirPath = getAbsolutePathForApp(dirPath);
|
||||
|
||||
// Generate a unique ID for this upload operation
|
||||
// This will be used to uniquely identify this operation and its progress
|
||||
// across servers and clients
|
||||
const operation_id = utils.uuidv4();
|
||||
|
||||
// Call 'init' callback if provided
|
||||
// init is basically a hook that allows the user to get the operation ID and the XMLHttpRequest object
|
||||
if(options.init && typeof options.init === 'function'){
|
||||
options.init(operation_id, xhr);
|
||||
}
|
||||
|
||||
// keeps track of the amount of data uploaded to the server
|
||||
let bytes_uploaded_to_server = 0;
|
||||
// keeps track of the amount of data uploaded to the cloud
|
||||
let bytes_uploaded_to_cloud = 0;
|
||||
|
||||
// This will hold the normalized entries to be uploaded
|
||||
// Since 'items' could be a DataTransferItemList, FileList, File, or an array of any of these,
|
||||
// we need to normalize it into an array of consistently formatted objects which will be held in 'entries'
|
||||
let entries;
|
||||
|
||||
// will hold the total size of the upload
|
||||
let total_size = 0;
|
||||
let file_count = 0;
|
||||
|
||||
let seemsToBeParsedDataTransferItems = false;
|
||||
if(Array.isArray(items) && items.length > 0){
|
||||
for(let i=0; i<items.length; i++){
|
||||
if(items[i] instanceof DataTransferItem || items[i] instanceof DataTransferItemList){
|
||||
seemsToBeParsedDataTransferItems = true;
|
||||
}else{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataTransferItemList
|
||||
if(items instanceof DataTransferItemList || items instanceof DataTransferItem || items[0] instanceof DataTransferItem || options.parsedDataTransferItems){
|
||||
// if parsedDataTransferItems is true, it means the user has already parsed the DataTransferItems
|
||||
if(options.parsedDataTransferItems)
|
||||
entries = items;
|
||||
else
|
||||
entries = await puter.ui.getEntriesFromDataTransferItems(items);
|
||||
|
||||
// Sort entries by size ascending
|
||||
entries.sort((entry_a, entry_b) => {
|
||||
if ( entry_a.isDirectory && ! entry_b.isDirectory ) return -1;
|
||||
if ( ! entry_a.isDirectory && entry_b.isDirectory ) return 1;
|
||||
if ( entry_a.isDirectory && entry_b.isDirectory ) return 0;
|
||||
|
||||
return entry_a.size - entry_b.size;
|
||||
});
|
||||
}
|
||||
// FileList/File
|
||||
else if(items instanceof File || items[0] instanceof File || items instanceof FileList || items[0] instanceof FileList){
|
||||
if(!Array.isArray(items))
|
||||
entries = items instanceof FileList ? Array.from(items) : [items];
|
||||
else
|
||||
entries = items;
|
||||
|
||||
// Sort entries by size ascending
|
||||
entries.sort((entry_a, entry_b) => {
|
||||
return entry_a.size - entry_b.size;
|
||||
})
|
||||
// add FullPath property to each entry
|
||||
for(let i=0; i<entries.length; i++){
|
||||
entries[i].filepath = entries[i].name;
|
||||
entries[i].fullPath = entries[i].name;
|
||||
}
|
||||
}
|
||||
// blob
|
||||
else if(items instanceof Blob){
|
||||
// create a File object from the blob
|
||||
let file = new File([items], options.name, { type: "text/plain" });
|
||||
entries = [file];
|
||||
// add FullPath property to each entry
|
||||
for(let i=0; i<entries.length; i++){
|
||||
entries[i].filepath = entries[i].name;
|
||||
entries[i].fullPath = entries[i].name;
|
||||
}
|
||||
}
|
||||
// String
|
||||
else if(typeof items === 'string'){
|
||||
// create a File object from the string
|
||||
let file = new File([items], 'default.txt', { type: "text/plain" });
|
||||
entries = [file];
|
||||
// add FullPath property to each entry
|
||||
for(let i=0; i<entries.length; i++){
|
||||
entries[i].filepath = entries[i].name;
|
||||
entries[i].fullPath = entries[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
// Will hold directories and files to be uploaded
|
||||
let dirs = [];
|
||||
let files = [];
|
||||
|
||||
// Separate files from directories
|
||||
for(let i=0; i<entries.length; i++){
|
||||
// skip empty entries
|
||||
if(!entries[i])
|
||||
continue;
|
||||
//collect dirs
|
||||
if(entries[i].isDirectory)
|
||||
dirs.push({path: path.join(dirPath, entries[i].finalPath ? entries[i].finalPath : entries[i].fullPath)});
|
||||
// also files
|
||||
else
|
||||
files.push(entries[i])
|
||||
// stats about the upload to come
|
||||
if(entries[i].size !== undefined){
|
||||
total_size += (entries[i].size);
|
||||
file_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue only if there are actually any files/directories to upload
|
||||
if(dirs.length === 0 && files.length === 0){
|
||||
if(options.error && typeof options.error === 'function'){
|
||||
options.error({code: 'EMPTY_UPLOAD', message: 'No files or directories to upload.'});
|
||||
}
|
||||
return reject({code: 'EMPTY_UPLOAD', message: 'No files or directories to upload.'});
|
||||
}
|
||||
|
||||
// Check storage capacity.
|
||||
// We need to check the storage capacity before the upload starts because
|
||||
// we want to avoid uploading files in case there is not enough storage space.
|
||||
// If we didn't check before upload starts, we could end up in a scenario where
|
||||
// the user uploads a very large folder/file and then the server rejects it because there is not enough space
|
||||
//
|
||||
// Space check in 'web' environment is currently not supported since it requires permissions.
|
||||
let storage;
|
||||
if(puter.env !== 'web'){
|
||||
try{
|
||||
storage = await this.space();
|
||||
if(storage.capacity - storage.used < total_size){
|
||||
if(options.error && typeof options.error === 'function'){
|
||||
options.error({code: 'NOT_ENOUGH_SPACE', message: 'Not enough storage space available.'});
|
||||
}
|
||||
return reject({code: 'NOT_ENOUGH_SPACE', message: 'Not enough storage space available.'});
|
||||
}
|
||||
}catch(e){
|
||||
}
|
||||
}
|
||||
|
||||
// total size of the upload is doubled because we will be uploading the files to the server
|
||||
// and then the server will upload them to the cloud
|
||||
total_size = total_size * 2;
|
||||
|
||||
// holds the data to be sent to the server
|
||||
const fd = new FormData();
|
||||
|
||||
//-------------------------------------------------
|
||||
// Generate the requests to create all the
|
||||
// folders in this upload
|
||||
//-------------------------------------------------
|
||||
dirs.sort();
|
||||
let mkdir_requests = [];
|
||||
|
||||
for(let i=0; i < dirs.length; i++){
|
||||
// update all file paths under this folder if dirname was changed
|
||||
for(let j=0; j<files.length; j++){
|
||||
// if file is in this folder and has not been processed yet
|
||||
if(!files[j].puter_path_param && path.join(dirPath, files[j].filepath).startsWith((dirs[i].path) + '/')){
|
||||
files[j].puter_path_param = `$dir_${i}/`+ path.basename(files[j].filepath);
|
||||
}
|
||||
}
|
||||
|
||||
// update all subdirs under this dir
|
||||
for(let k=0; k < dirs.length; k++){
|
||||
if(!dirs[k].puter_path_param && dirs[k].path.startsWith(dirs[i].path + '/')){
|
||||
dirs[k].puter_path_param = `$dir_${i}/`+ path.basename(dirs[k].path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(let i=0; i < dirs.length; i++){
|
||||
let parent_path = path.dirname(dirs[i].puter_path_param || dirs[i].path);
|
||||
let dir_path = dirs[i].puter_path_param || dirs[i].path;
|
||||
|
||||
// remove parent path from the beginning of path since path is relative to parent
|
||||
if(parent_path !== '/')
|
||||
dir_path = dir_path.replace(parent_path, '');
|
||||
|
||||
mkdir_requests.push({
|
||||
op: 'mkdir',
|
||||
parent: parent_path,
|
||||
path: dir_path,
|
||||
overwrite: options.overwrite ?? false,
|
||||
dedupe_name: options.dedupeName ?? true,
|
||||
create_missing_ancestors: options.createMissingAncestors ?? true,
|
||||
as: `dir_${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
// inverse mkdir_requests so that the root folder is created first
|
||||
// and then go down the tree
|
||||
mkdir_requests.reverse();
|
||||
|
||||
fd.append('operation_id', operation_id);
|
||||
fd.append('socket_id', this.socket.id);
|
||||
fd.append('original_client_socket_id', this.socket.id);
|
||||
|
||||
// Append mkdir operations to upload request
|
||||
for(let i=0; i<mkdir_requests.length; i++){
|
||||
fd.append('operation', JSON.stringify(mkdir_requests[i]));
|
||||
}
|
||||
|
||||
// Append file metadata to upload request
|
||||
if(!options.shortcutTo){
|
||||
for(let i=0; i<files.length; i++){
|
||||
fd.append('fileinfo', JSON.stringify({
|
||||
name: files[i].name,
|
||||
type: files[i].type,
|
||||
size: files[i].size,
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Append write operations for each file
|
||||
for(let i=0; i<files.length; i++){
|
||||
fd.append('operation', JSON.stringify({
|
||||
op: options.shortcutTo ? 'shortcut' : 'write',
|
||||
dedupe_name: options.dedupeName ?? true,
|
||||
overwrite: options.overwrite ?? false,
|
||||
create_missing_ancestors: (options.createMissingAncestors || options.createMissingParents),
|
||||
operation_id: operation_id,
|
||||
path: (
|
||||
files[i].puter_path_param &&
|
||||
path.dirname(files[i].puter_path_param ?? '')
|
||||
) || (
|
||||
files[i].filepath &&
|
||||
path.join(dirPath, path.dirname(files[i].filepath))
|
||||
) || '',
|
||||
name: path.basename(files[i].filepath),
|
||||
item_upload_id: i,
|
||||
shortcut_to: options.shortcutTo,
|
||||
shortcut_to_uid: options.shortcutTo,
|
||||
app_uid: options.appUID,
|
||||
}));
|
||||
}
|
||||
|
||||
// Append files to upload
|
||||
if(!options.shortcutTo){
|
||||
for(let i=0; i<files.length; i++){
|
||||
fd.append('file', files[i] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
const progress_handler = (msg) => {
|
||||
if(msg.operation_id === operation_id){
|
||||
bytes_uploaded_to_cloud += msg.loaded_diff
|
||||
}
|
||||
}
|
||||
|
||||
// Handle upload progress events from server
|
||||
this.socket.on('upload.progress', progress_handler);
|
||||
|
||||
// keeps track of the amount of data uploaded to the server
|
||||
let previous_chunk_uploaded = null;
|
||||
|
||||
// open request to server
|
||||
xhr.open("post",(this.APIOrigin +'/batch'), true);
|
||||
// set auth header
|
||||
xhr.setRequestHeader("Authorization", "Bearer " + this.authToken);
|
||||
|
||||
// -----------------------------------------------
|
||||
// Upload progress: client -> server
|
||||
// -----------------------------------------------
|
||||
xhr.upload.addEventListener('progress', function(e){
|
||||
// update operation tracker
|
||||
let chunk_uploaded;
|
||||
if(previous_chunk_uploaded === null){
|
||||
chunk_uploaded = e.loaded;
|
||||
previous_chunk_uploaded = 0;
|
||||
}else{
|
||||
chunk_uploaded = e.loaded - previous_chunk_uploaded;
|
||||
}
|
||||
previous_chunk_uploaded += chunk_uploaded;
|
||||
bytes_uploaded_to_server += chunk_uploaded;
|
||||
|
||||
// overall operation progress
|
||||
let op_progress = ((bytes_uploaded_to_cloud + bytes_uploaded_to_server)/total_size * 100).toFixed(2);
|
||||
op_progress = op_progress > 100 ? 100 : op_progress;
|
||||
|
||||
// progress callback function
|
||||
if(options.progress && typeof options.progress === 'function')
|
||||
options.progress(operation_id, op_progress);
|
||||
})
|
||||
|
||||
// -----------------------------------------------
|
||||
// Upload progress: server -> cloud
|
||||
// the following code will check the progress of the upload every 100ms
|
||||
// -----------------------------------------------
|
||||
let cloud_progress_check_interval = setInterval(function() {
|
||||
// operation progress
|
||||
let op_progress = ((bytes_uploaded_to_cloud + bytes_uploaded_to_server)/total_size * 100).toFixed(2);
|
||||
|
||||
op_progress = op_progress > 100 ? 100 : op_progress;
|
||||
if(options.progress && typeof options.progress === 'function')
|
||||
options.progress(operation_id, op_progress);
|
||||
}, 100);
|
||||
|
||||
// -----------------------------------------------
|
||||
// onabort
|
||||
// -----------------------------------------------
|
||||
xhr.onabort = ()=>{
|
||||
// stop the cloud upload progress tracker
|
||||
clearInterval(cloud_progress_check_interval);
|
||||
// remove progress handler
|
||||
this.socket.off('upload.progress', progress_handler);
|
||||
// if an 'abort' callback is provided, call it
|
||||
if(options.abort && typeof options.abort === 'function')
|
||||
options.abort(operation_id);
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// on success/error
|
||||
// -----------------------------------------------
|
||||
xhr.onreadystatechange = async (e)=>{
|
||||
if (xhr.readyState === 4) {
|
||||
const resp = await utils.parseResponse(xhr);
|
||||
// Error
|
||||
if((xhr.status >= 400 && xhr.status < 600) || (options.strict && xhr.status === 218)) {
|
||||
// stop the cloud upload progress tracker
|
||||
clearInterval(cloud_progress_check_interval);
|
||||
|
||||
// remove progress handler
|
||||
this.socket.off('upload.progress', progress_handler);
|
||||
|
||||
// If this is a 'strict' upload (i.e. status code is 218), we need to find out which operation failed
|
||||
// and call the error callback with that operation.
|
||||
if(options.strict && xhr.status === 218){
|
||||
// find the operation that failed
|
||||
let failed_operation;
|
||||
for(let i=0; i<resp.results?.length; i++){
|
||||
if(resp.results[i].status !== 200){
|
||||
failed_operation = resp.results[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if error callback is provided, call it
|
||||
if(options.error && typeof options.error === 'function'){
|
||||
options.error(failed_operation);
|
||||
}
|
||||
return reject(failed_operation);
|
||||
}
|
||||
|
||||
// if error callback is provided, call it
|
||||
if(options.error && typeof options.error === 'function'){
|
||||
options.error(resp);
|
||||
}
|
||||
return reject(resp);
|
||||
}
|
||||
// Success
|
||||
else{
|
||||
if(!resp || !resp.results || resp.results.length === 0){
|
||||
// no results
|
||||
console.log('no results');
|
||||
}
|
||||
|
||||
let items = resp.results;
|
||||
items = items.length === 1 ? items[0] : items;
|
||||
|
||||
// if success callback is provided, call it
|
||||
if(options.success && typeof options.success === 'function'){
|
||||
options.success(items);
|
||||
}
|
||||
// stop the cloud upload progress tracker
|
||||
clearInterval(cloud_progress_check_interval);
|
||||
// remove progress handler
|
||||
this.socket.off('upload.progress', progress_handler);
|
||||
|
||||
return resolve(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire off the 'start' event
|
||||
if(options.start && typeof options.start === 'function'){
|
||||
options.start();
|
||||
}
|
||||
|
||||
// send request
|
||||
xhr.send(fd);
|
||||
})
|
||||
}
|
||||
|
||||
export default upload;
|
@ -0,0 +1,53 @@
|
||||
import path from "../../../lib/path.js"
|
||||
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
||||
|
||||
const write = async function (targetPath, data, options = {}) {
|
||||
// targetPath is required
|
||||
if(!targetPath){
|
||||
throw new Error({ code: 'NO_TARGET_PATH', message: 'No target path provided.' });
|
||||
}
|
||||
// if targetPath is a File
|
||||
if(targetPath instanceof File && data === undefined){
|
||||
data = targetPath;
|
||||
targetPath = data.name;
|
||||
}
|
||||
|
||||
// strict mode will cause the upload to fail if even one operation fails
|
||||
// for example, if one of the files in a folder fails to upload, the entire upload will fail
|
||||
// since write is a wrapper around upload to handle single-file uploads, we need to pass the strict option to upload
|
||||
options.strict = true;
|
||||
|
||||
// by default, we want to overwrite existing files
|
||||
options.overwrite = options.overwrite ?? true;
|
||||
|
||||
// if overwrite is true and dedupeName is not provided, set dedupeName to false
|
||||
if(options.overwrite && options.dedupeName === undefined)
|
||||
options.dedupeName = false;
|
||||
|
||||
// if targetPath is not provided or it's not starting with a slash, it means it's a relative path
|
||||
// in that case, we need to prepend the app's root directory to it
|
||||
targetPath = getAbsolutePathForApp(targetPath);
|
||||
|
||||
// extract file name from targetPath
|
||||
const filename = path.basename(targetPath);
|
||||
|
||||
// extract the parent directory from targetPath
|
||||
const parent = path.dirname(targetPath);
|
||||
|
||||
// if data is a string, convert it to a File object
|
||||
if(typeof data === 'string'){
|
||||
data = new File([data ?? ''], filename ?? 'Untitled.txt', { type: "text/plain" });
|
||||
}
|
||||
// blob
|
||||
else if(data instanceof Blob){
|
||||
data = new File([data ?? ''], filename ?? 'Untitled', { type: data.type });
|
||||
}
|
||||
|
||||
if(!data)
|
||||
data = new File([data ?? ''], filename);
|
||||
|
||||
// perform upload
|
||||
return this.upload(data, parent, options);
|
||||
}
|
||||
|
||||
export default write;
|
@ -0,0 +1,21 @@
|
||||
import path from "../../../lib/path.js"
|
||||
|
||||
const getAbsolutePathForApp = (relativePath)=>{
|
||||
// if we are in the gui environment, return the relative path as is
|
||||
if(puter.env === 'gui')
|
||||
return relativePath;
|
||||
|
||||
// if no relative path is provided, use the current working directory
|
||||
if(!relativePath)
|
||||
relativePath = '.';
|
||||
|
||||
// If relativePath is not provided, or it's not starting with a slash or tilde,
|
||||
// it means it's a relative path. In that case, prepend the app's root directory.
|
||||
if (!relativePath || (!relativePath.startsWith('/') && !relativePath.startsWith('~') && puter.appID)) {
|
||||
relativePath = path.join('~/AppData', puter.appID, relativePath);
|
||||
}
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
export default getAbsolutePathForApp;
|
136
packages/puter-dot-js/src/modules/Hosting.js
Normal file
136
packages/puter-dot-js/src/modules/Hosting.js
Normal file
@ -0,0 +1,136 @@
|
||||
import * as utils from '../lib/utils.js';
|
||||
import getAbsolutePathForApp from './FileSystem/utils/getAbsolutePathForApp.js';
|
||||
|
||||
class Hosting{
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [Router]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [Apps]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
// todo document the `Subdomain` object.
|
||||
list = utils.make_driver_method([], 'puter-subdomains', 'select');
|
||||
|
||||
create = async (...args) => {
|
||||
let options = {};
|
||||
// * allows for: puter.hosting.new('example-subdomain') *
|
||||
if (typeof args[0] === 'string' && args.length === 1) {
|
||||
// if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
|
||||
// and use it as the subdomain. This is to make development easier.
|
||||
if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
|
||||
args[0] = args[0].split('.')[0];
|
||||
}
|
||||
|
||||
options = { object: { subdomain: args[0] }};
|
||||
}
|
||||
// if there are two string arguments, assume they are the subdomain and the target directory
|
||||
// * allows for: puter.hosting.new('example-subdomain', '/path/to/target') *
|
||||
else if (Array.isArray(args) && args.length === 2 && typeof args[0] === 'string') {
|
||||
// if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
|
||||
// and use it as the subdomain. This is to make development easier.
|
||||
if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
|
||||
args[0] = args[0].split('.')[0];
|
||||
}
|
||||
|
||||
// if the target directory is not an absolute path, make it an absolute path relative to the app's root directory
|
||||
if(args[1]){
|
||||
args[1] = getAbsolutePathForApp(args[1]);
|
||||
}
|
||||
|
||||
options = { object: { subdomain: args[0], root_dir: args[1] }};
|
||||
}
|
||||
// allows for: puter.hosting.new({ subdomain: 'subdomain' })
|
||||
else if (typeof args[0] === 'object') {
|
||||
options = { object: args[0] };
|
||||
}
|
||||
// Call the original chat.complete method
|
||||
return await utils.make_driver_method(['object'], 'puter-subdomains', 'create').call(this, options);
|
||||
}
|
||||
|
||||
update = async(...args) => {
|
||||
let options = {};
|
||||
// If there are two string arguments, assume they are the subdomain and the target directory
|
||||
// * allows for: puter.hosting.update('example-subdomain', '/path/to/target') *
|
||||
if (Array.isArray(args) && typeof args[0] === 'string') {
|
||||
// if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
|
||||
// and use it as the subdomain. This is to make development easier.
|
||||
if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
|
||||
args[0] = args[0].split('.')[0];
|
||||
}
|
||||
|
||||
// if the target directory is not an absolute path, make it an absolute path relative to the app's root directory
|
||||
if(args[1]){
|
||||
args[1] = getAbsolutePathForApp(args[1]);
|
||||
}
|
||||
|
||||
options = { id: {subdomain: args[0]}, object: { root_dir: args[1] ?? null }};
|
||||
}
|
||||
|
||||
// Call the original chat.complete method
|
||||
return await utils.make_driver_method(['object'], 'puter-subdomains', 'update').call(this, options);
|
||||
}
|
||||
|
||||
get = async(...args) => {
|
||||
let options = {};
|
||||
// if there is one string argument, assume it is the subdomain
|
||||
// * allows for: puter.hosting.get('example-subdomain') *
|
||||
if (Array.isArray(args) && typeof args[0] === 'string') {
|
||||
// if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
|
||||
// and use it as the subdomain. This is to make development easier.
|
||||
if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
|
||||
args[0] = args[0].split('.')[0];
|
||||
}
|
||||
|
||||
options = { id: {subdomain: args[0]}};
|
||||
}
|
||||
return utils.make_driver_method(['uid'], 'puter-subdomains', 'read').call(this, options);
|
||||
}
|
||||
|
||||
delete = async(...args) => {
|
||||
let options = {};
|
||||
// if there is one string argument, assume it is the subdomain
|
||||
// * allows for: puter.hosting.get('example-subdomain') *
|
||||
if (Array.isArray(args) && typeof args[0] === 'string') {
|
||||
// if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
|
||||
// and use it as the subdomain. This is to make development easier.
|
||||
if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
|
||||
args[0] = args[0].split('.')[0];
|
||||
}
|
||||
|
||||
options = { id: {subdomain: args[0]}};
|
||||
}
|
||||
return utils.make_driver_method(['uid'], 'puter-subdomains', 'delete').call(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
export default Hosting;
|
205
packages/puter-dot-js/src/modules/KV.js
Normal file
205
packages/puter-dot-js/src/modules/KV.js
Normal file
@ -0,0 +1,205 @@
|
||||
import * as utils from '../lib/utils.js'
|
||||
|
||||
class KV{
|
||||
MAX_KEY_SIZE = 1024;
|
||||
MAX_VALUE_SIZE = 400 * 1024;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [KV]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [KV]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to 'true' on success, or rejects with an error on failure
|
||||
*
|
||||
* `key` cannot be undefined or null.
|
||||
* `key` size cannot be larger than 1mb.
|
||||
* `value` size cannot be larger than 10mb.
|
||||
*/
|
||||
set = utils.make_driver_method(['key', 'value'], 'puter-kvstore', 'set',{
|
||||
preprocess: (args)=>{
|
||||
// key cannot be undefined or null
|
||||
if(args.key === undefined || args.key === null){
|
||||
throw { message: 'Key cannot be undefined', code: 'key_undefined'};
|
||||
}
|
||||
// key size cannot be larger than MAX_KEY_SIZE
|
||||
if(args.key.length > this.MAX_KEY_SIZE){
|
||||
throw {message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'};
|
||||
}
|
||||
// value size cannot be larger than MAX_VALUE_SIZE
|
||||
if(args.value && args.value.length > this.MAX_VALUE_SIZE){
|
||||
throw {message: 'Value size cannot be larger than ' + this.MAX_VALUE_SIZE, code: 'value_too_large'};
|
||||
}
|
||||
return args;
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Resolves to the value if the key exists, or `undefined` if the key does not exist. Rejects with an error on failure.
|
||||
*/
|
||||
get = utils.make_driver_method(['key'], 'puter-kvstore', 'get', {
|
||||
preprocess: (args)=>{
|
||||
// key size cannot be larger than MAX_KEY_SIZE
|
||||
if(args.key.length > this.MAX_KEY_SIZE){
|
||||
throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
|
||||
}
|
||||
|
||||
return args;
|
||||
},
|
||||
transform: (res)=>{
|
||||
return res;
|
||||
}
|
||||
})
|
||||
|
||||
incr = async(...args) => {
|
||||
let options = {};
|
||||
|
||||
// arguments are required
|
||||
if(!args || args.length === 0){
|
||||
throw ({message: 'Arguments are required', code: 'arguments_required'});
|
||||
}
|
||||
|
||||
options.key = args[0];
|
||||
options.amount = args[1] ?? 1;
|
||||
|
||||
// key size cannot be larger than MAX_KEY_SIZE
|
||||
if(options.key.length > this.MAX_KEY_SIZE){
|
||||
throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
|
||||
}
|
||||
|
||||
return utils.make_driver_method(['key'], 'puter-kvstore', 'incr').call(this, options);
|
||||
}
|
||||
|
||||
decr = async(...args) => {
|
||||
let options = {};
|
||||
|
||||
// arguments are required
|
||||
if(!args || args.length === 0){
|
||||
throw ({message: 'Arguments are required', code: 'arguments_required'});
|
||||
}
|
||||
|
||||
options.key = args[0];
|
||||
options.amount = args[1] ?? 1;
|
||||
|
||||
// key size cannot be larger than MAX_KEY_SIZE
|
||||
if(options.key.length > this.MAX_KEY_SIZE){
|
||||
throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
|
||||
}
|
||||
|
||||
return utils.make_driver_method(['key'], 'puter-kvstore', 'decr').call(this, options);
|
||||
}
|
||||
|
||||
// resolves to 'true' on success, or rejects with an error on failure
|
||||
// will still resolve to 'true' if the key does not exist
|
||||
del = utils.make_driver_method(['key'], 'puter-kvstore', 'del', {
|
||||
preprocess: (args)=>{
|
||||
// key size cannot be larger than this.MAX_KEY_SIZE
|
||||
if(args.key.length > this.MAX_KEY_SIZE){
|
||||
throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
});
|
||||
|
||||
list = async(...args) => {
|
||||
let options = {};
|
||||
let pattern;
|
||||
let returnValues = false;
|
||||
|
||||
// list(true) or list(pattern, true) will return the key-value pairs
|
||||
if((args && args.length === 1 && args[0] === true) || (args && args.length === 2 && args[1] === true)){
|
||||
options = {};
|
||||
returnValues = true;
|
||||
}
|
||||
// return only the keys, default behavior
|
||||
else{
|
||||
options = { as: 'keys'};
|
||||
}
|
||||
|
||||
// list(pattern)
|
||||
// list(pattern, true)
|
||||
if((args && args.length === 1 && typeof args[0] === 'string') || (args && args.length === 2 && typeof args[0] === 'string' && args[1] === true)){
|
||||
pattern = args[0];
|
||||
}
|
||||
|
||||
return utils.make_driver_method([], 'puter-kvstore', 'list', {
|
||||
transform: (res)=>{
|
||||
// glob pattern was provided
|
||||
if(pattern){
|
||||
// consider both the key and the value
|
||||
if(!returnValues) {
|
||||
let keys = res.filter((key)=>{
|
||||
return globMatch(pattern, key);
|
||||
});
|
||||
return keys;
|
||||
}else{
|
||||
let keys = res.filter((key_value_pair)=>{
|
||||
return globMatch(pattern, key_value_pair.key);
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}).call(this, options);
|
||||
}
|
||||
|
||||
// resolve to 'true' on success, or rejects with an error on failure
|
||||
// will still resolve to 'true' if there are no keys
|
||||
flush = utils.make_driver_method([], 'puter-kvstore', 'flush')
|
||||
|
||||
// clear is an alias for flush
|
||||
clear = this.flush;
|
||||
}
|
||||
|
||||
|
||||
function globMatch(pattern, str) {
|
||||
const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
let regexPattern = escapeRegExp(pattern)
|
||||
.replace(/\\\*/g, '.*') // Replace * with .*
|
||||
.replace(/\\\?/g, '.') // Replace ? with .
|
||||
.replace(/\\\[/g, '[') // Replace [ with [
|
||||
.replace(/\\\]/g, ']') // Replace ] with ]
|
||||
.replace(/\\\^/g, '^'); // Replace ^ with ^
|
||||
|
||||
let re = new RegExp('^' + regexPattern + '$');
|
||||
return re.test(str);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default KV
|
90
packages/puter-dot-js/src/modules/OS.js
Normal file
90
packages/puter-dot-js/src/modules/OS.js
Normal file
@ -0,0 +1,90 @@
|
||||
import * as utils from '../lib/utils.js'
|
||||
|
||||
class OS{
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
* @class
|
||||
* @param {string} authToken - Token used to authenticate the user.
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (authToken, APIOrigin, appID) {
|
||||
this.authToken = authToken;
|
||||
this.APIOrigin = APIOrigin;
|
||||
this.appID = appID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new authentication token.
|
||||
*
|
||||
* @param {string} authToken - The new authentication token.
|
||||
* @memberof [OS]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [Apps]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
user = function(...args){
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = utils.initXhr('/whoami', this.APIOrigin, this.authToken, 'get');
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
|
||||
version = function(...args){
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
options = {
|
||||
success: args[0],
|
||||
error: args[1],
|
||||
// Add more if needed...
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = utils.initXhr('/version', this.APIOrigin, this.authToken, 'get');
|
||||
|
||||
// set up event handlers for load and error events
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default OS
|
373
packages/puter-dot-js/src/modules/PuterDialog.js
Normal file
373
packages/puter-dot-js/src/modules/PuterDialog.js
Normal file
File diff suppressed because one or more lines are too long
1019
packages/puter-dot-js/src/modules/UI.js
Normal file
1019
packages/puter-dot-js/src/modules/UI.js
Normal file
File diff suppressed because it is too large
Load Diff
619
packages/puter-dot-js/test/fs.test.js
Normal file
619
packages/puter-dot-js/test/fs.test.js
Normal file
@ -0,0 +1,619 @@
|
||||
naughtyStrings = [
|
||||
"文件.txt", // Chinese characters
|
||||
"файл.txt", // Cyrillic characters
|
||||
"ファイル.txt", // Japanese characters
|
||||
"파일.txt", // Korean characters
|
||||
"ملف.txt", // Arabic characters
|
||||
"फ़ाइल.txt", // Hindi characters
|
||||
"archivo.txt", // Spanish characters
|
||||
"fichier.txt", // French characters
|
||||
"αρχείο.txt", // Greek characters
|
||||
"datei.txt", // German characters
|
||||
"fil.txt", // Swedish characters
|
||||
"קובץ.txt", // Hebrew characters
|
||||
"文件名.txt", // Chinese characters
|
||||
"файлы.txt", // Russian characters
|
||||
"फ़ाइलें.txt", // Hindi characters
|
||||
"📄_emoji.txt", // Emoji
|
||||
"file name with spaces.txt",
|
||||
"file-name-with-dashes.txt",
|
||||
"file_name_with_underscores.txt",
|
||||
"file.name.with.periods.txt",
|
||||
"file,name,with,commas.txt",
|
||||
"file;name;with;semicolons.txt",
|
||||
"file(name)with(parentheses).txt",
|
||||
"file[name]with[brackets].txt",
|
||||
"file{name}with{braces}.txt",
|
||||
"file!name!with!exclamations!.txt",
|
||||
"file@name@with@ats.txt",
|
||||
"file#name#with#hashes#.txt",
|
||||
"file$name$with$dollars$.txt",
|
||||
"file%name%with%percentages%.txt",
|
||||
"file^name^with^carats^.txt",
|
||||
"file&name&with&s&.txt",
|
||||
"file*name*with*asterisks*.txt",
|
||||
"file_name_with_long_name_exceeding_255_characters_abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.txt",
|
||||
"file👍name👍with👍thumbs👍up.txt",
|
||||
"file😂name😂with😂emojis😂.txt",
|
||||
"file🌍name🌍with🌍globe🌍emojis🌍.txt",
|
||||
"file🔥name🔥with🔥fire🔥emoji🔥.txt",
|
||||
"file🎉name🎉with🎉party🎉popper🎉emoji🎉.txt",
|
||||
"file💼name💼with💼briefcase💼emoji💼.txt",
|
||||
"file🍔name🍔with🍔burger🍔emoji🍔.txt",
|
||||
"file🚀name🚀with🚀rocket🚀emoji🚀.txt",
|
||||
"file👽name👽with👽alien👽emoji👽.txt",
|
||||
"file🌈name🌈with🌈rainbow🌈emoji🌈.txt",
|
||||
"file🍆name🍆with🍆eggplant🍆emoji🍆.txt",
|
||||
"file🍑name🍑with🍑peach🍑emoji🍑.txt",
|
||||
"invisible\u200Bname.txt", // Invisible Unicode character (Zero Width Space)
|
||||
"invisible\u200Cname.txt", // Invisible Unicode character (Zero Width Non-Joiner)
|
||||
"invisible\u200Dname.txt", // Invisible Unicode character (Zero Width Joiner)
|
||||
"invisible\uFEFFname.txt", // Invisible Unicode character (Zero Width No-Break Space)
|
||||
"invisible\u180Ename.txt", // Invisible Unicode character (Mongolian Vowel Separator)
|
||||
"hash#tag.txt",
|
||||
"percent%20encoded.txt",
|
||||
"plus+sign.txt",
|
||||
"ampersand&symbol.txt",
|
||||
"at@symbol.txt",
|
||||
"parentheses(1).txt",
|
||||
"brackets[1].txt",
|
||||
"curly{braces}.txt",
|
||||
"angle<tags>.txt",
|
||||
"exclamation!point.txt",
|
||||
"question?mark.txt",
|
||||
"colon:separated.txt",
|
||||
"semicolon;separated.txt",
|
||||
"single'quote.txt",
|
||||
"double\"quote.txt",
|
||||
"backtick`char.txt",
|
||||
"tilde~sign.txt",
|
||||
"underscore_character.txt",
|
||||
"hyphen-character.txt",
|
||||
"equal=sign.txt",
|
||||
"plus+sign.txt",
|
||||
"asterisk*char.txt",
|
||||
"caret^char.txt",
|
||||
"percent%sign.txt",
|
||||
"dollar$sign.txt",
|
||||
"pound#sign.txt",
|
||||
"at@sign.txt",
|
||||
"exclamation!mark.txt",
|
||||
"question?mark.txt",
|
||||
"backslash\\char.txt",
|
||||
"pipe|char.txt",
|
||||
"colon:char.txt",
|
||||
"semicolon;char.txt",
|
||||
"quote'char.txt",
|
||||
"double\"quote.txt",
|
||||
"backtick`char.txt",
|
||||
"braces{char}.txt",
|
||||
"brackets[char].txt",
|
||||
"parentheses(char).txt",
|
||||
"angle<brackets>.txt",
|
||||
"ellipsis….txt",
|
||||
"accentué.txt",
|
||||
"ümlaut.txt",
|
||||
"tildeñ.txt",
|
||||
"çedilla.txt",
|
||||
"špecial.txt",
|
||||
"russianЯ.txt",
|
||||
"chinese中文.txt",
|
||||
"arabicعربى.txt",
|
||||
"hebrewעברית.txt",
|
||||
"japanese日本語.txt",
|
||||
"korean한국어.txt",
|
||||
"vietnameseTiếng Việt.txt",
|
||||
|
||||
]
|
||||
|
||||
window.fsTests = [
|
||||
testFSWrite = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
const result = await puter.fs.write(randName, 'testValue');
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWrite passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
throw("testFSWrite failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw("testFSWrite failed:", error);
|
||||
}
|
||||
},
|
||||
testFSRead = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await (await puter.fs.read(randName)).text();
|
||||
assert(result === 'testValue', "Failed to read from file");
|
||||
pass("testFSRead passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSRead failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSRead failed:", error);
|
||||
}
|
||||
},
|
||||
testFSWriteWithoutData = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
const result = await puter.fs.write(randName);
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWriteWithoutData passed");
|
||||
if(randName !== result.name) {
|
||||
fail(`testFSWriteWithoutData failed: Names do not match ${randName} ${result.name}`);
|
||||
}
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSWriteWithoutData failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSWriteWithoutData failed:", error);
|
||||
}
|
||||
},
|
||||
testFSReadWithoutData = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName);
|
||||
const result = await (await puter.fs.read(randName)).text();
|
||||
assert(result === '', "Failed to read from file");
|
||||
pass("testFSReadWithoutData passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSReadWithoutData failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSReadWithoutData failed:", error);
|
||||
}
|
||||
},
|
||||
testFSWriteToExistingFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await puter.fs.write(randName, 'updatedValue');
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWriteToExistingFile passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFile failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFile failed:", error);
|
||||
}
|
||||
},
|
||||
testFSWriteToExistingFileWithoutOverwriteAndDedupe = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await puter.fs.write(randName, 'updatedValue', { overwrite: false, dedupeName: false });
|
||||
assert(!result.uid, "Failed to write to file");
|
||||
fail("testFSWriteToExistingFileWithoutOverwriteAndDedupe failed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFileWithoutOverwriteAndDedupe failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
pass("testFSWriteToExistingFileWithoutOverwriteAndDedupe passed");
|
||||
}
|
||||
|
||||
},
|
||||
testFSWriteToExistingFileWithoutOverwriteButWithDedupe = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await puter.fs.write(randName, 'updatedValue', { overwrite: false, dedupeName: true });
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWriteToExistingFileWithoutOverwriteButWithDedupe passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFileWithoutOverwriteButWithDedupe failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFileWithoutOverwriteButWithDedupe failed:", error);
|
||||
}
|
||||
},
|
||||
testFSWriteToExistingFileWithOverwriteButWithoutDedupe = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await puter.fs.write(randName, 'updatedValue', { overwrite: true, dedupeName: false });
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWriteToExistingFileWithOverwriteButWithoutDedupe passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFileWithOverwriteButWithoutDedupe failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSWriteToExistingFileWithOverwriteButWithoutDedupe failed:", error);
|
||||
}
|
||||
},
|
||||
// create a directory
|
||||
testFSCreateDir = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
const result = await puter.fs.mkdir(randName);
|
||||
assert(result.uid, "Failed to create directory");
|
||||
pass("testFSCreateDir passed");
|
||||
} catch (error) {
|
||||
fail("testFSCreateDir failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// write a number of files to a directory and list them
|
||||
testFSReadDir = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.mkdir(randName);
|
||||
await puter.fs.write(randName + '/file1', 'testValue');
|
||||
await puter.fs.write(randName + '/file2', 'testValue');
|
||||
await puter.fs.write(randName + '/file3', 'testValue');
|
||||
const result = await puter.fs.readdir(randName);
|
||||
assert(result.length === 3, "Failed to read directory");
|
||||
pass("testFSReadDir passed");
|
||||
} catch (error) {
|
||||
fail("testFSReadDir failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// create a file then delete it
|
||||
testFSDelete = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await puter.fs.delete(randName);
|
||||
assert(!result.uid, "Failed to delete file");
|
||||
pass("testFSDelete passed");
|
||||
} catch (error) {
|
||||
fail("testFSDelete failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// create a directory, write a number of files to it, then delete it
|
||||
testFSDeleteDir = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.mkdir(randName);
|
||||
await puter.fs.write(randName + '/file1', 'testValue');
|
||||
await puter.fs.write(randName + '/file2', 'testValue');
|
||||
await puter.fs.write(randName + '/file3', 'testValue');
|
||||
const result = await puter.fs.delete(randName);
|
||||
assert(!result.uid, "Failed to delete directory");
|
||||
pass("testFSDeleteDir passed");
|
||||
} catch (error) {
|
||||
fail("testFSDeleteDir failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// attempt to delete a non-existent file
|
||||
testFSDeleteNonExistentFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
const result = await puter.fs.delete(randName);
|
||||
assert(!result.uid, "Failed to delete non-existent file");
|
||||
pass("testFSDeleteNonExistentFile passed");
|
||||
} catch (error) {
|
||||
if(error.code !== "subject_does_not_exist")
|
||||
fail("testFSDeleteNonExistentFile failed:", error);
|
||||
else
|
||||
pass("testFSDeleteNonExistentFile passed");
|
||||
}
|
||||
},
|
||||
|
||||
// attempt to access a non-existent file
|
||||
testFSReadNonExistentFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
const result = await puter.fs.read(randName);
|
||||
fail("testFSReadNonExistentFile failed");
|
||||
} catch (error) {
|
||||
if(error.code !== "subject_does_not_exist")
|
||||
fail("testFSReadNonExistentFile failed:", error);
|
||||
else
|
||||
pass("testFSReadNonExistentFile passed");
|
||||
}
|
||||
},
|
||||
|
||||
testFSWriteWithSpecialCharacters = async ()=>{
|
||||
let randName
|
||||
try {
|
||||
randName = 'testFileWithSpecialCharacte rs!@#$%^&*()_+{}|:"<>?`~'
|
||||
const result = await puter.fs.write(randName, 'testValue', { specialCharacters: true });
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWriteWithSpecialCharacters passed");
|
||||
} catch (error) {
|
||||
fail("testFSWriteWithSpecialCharacters failed:", error);
|
||||
}
|
||||
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSWriteWithSpecialCharacters failed to delete file:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSReadWithSpecialCharacters = async ()=>{
|
||||
try {
|
||||
let randName = 'testFileWithSpecialCharacte rs!@#$%^&*()_+{}|:"<>?`~'
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await (await puter.fs.read(randName)).text();
|
||||
assert(result === 'testValue', "Failed to read from file");
|
||||
pass("testFSReadWithSpecialCharacters passed");
|
||||
} catch (error) {
|
||||
fail("testFSReadWithSpecialCharacters failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSWriteLargeFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
const result = await puter.fs.write(randName, 'testValue'.repeat(100000));
|
||||
assert(result.uid, "Failed to write to file");
|
||||
pass("testFSWriteLargeFile passed");
|
||||
} catch (error) {
|
||||
fail("testFSWriteLargeFile failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSReadLargeFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue'.repeat(100000));
|
||||
const result = await (await puter.fs.read(randName)).text();
|
||||
assert(result === 'testValue'.repeat(100000), "Failed to read from file");
|
||||
pass("testFSReadLargeFile passed");
|
||||
} catch (error) {
|
||||
fail("testFSReadLargeFile failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSRenameFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let randName2 = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
const result = await puter.fs.rename(randName, randName2);
|
||||
assert(result.name, "Failed to rename file");
|
||||
pass("testFSRenameFile passed");
|
||||
// check that the old file is gone
|
||||
try {
|
||||
await puter.fs.read(randName);
|
||||
fail("testFSRenameFile failed to delete old file");
|
||||
} catch (error) {
|
||||
if(error.code !== "subject_does_not_exist")
|
||||
fail("testFSRenameFile failed to delete old file:", error);
|
||||
else
|
||||
pass("testFSRenameFile passed");
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSRenameFile failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSMoveFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let randName2 = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
await puter.fs.mkdir(randName2);
|
||||
let result = await puter.fs.move(randName, randName2);
|
||||
assert(result.moved, "Failed to move file");
|
||||
// check that the old file is gone
|
||||
try {
|
||||
await puter.fs.read(randName);
|
||||
fail("testFSMoveFile failed to delete old file");
|
||||
} catch (error) {
|
||||
if(error.code !== "subject_does_not_exist")
|
||||
fail("testFSMoveFile failed to delete old file:", error);
|
||||
else
|
||||
pass("testFSMoveFile passed");
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSMoveFile failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSCopyFile = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let randName2 = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
await puter.fs.mkdir(randName2);
|
||||
let result = await puter.fs.copy(randName, randName2);
|
||||
assert(Array.isArray(result) && result[0].uid, "Failed to copy file");
|
||||
// check that the old file is still there
|
||||
try {
|
||||
await puter.fs.read(randName);
|
||||
pass("testFSCopyFile passed");
|
||||
} catch (error) {
|
||||
fail("testFSCopyFile failed to keep old file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSCopyFile failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// copy a file to a directory with newName option
|
||||
testFSCopyFileWithNewName = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let randName2 = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
await puter.fs.mkdir(randName2);
|
||||
let result = await puter.fs.copy(randName, randName2, { newName: 'newName' });
|
||||
assert(Array.isArray(result) && result[0].uid, "Failed to copy file");
|
||||
// check file name
|
||||
assert(result[0].name === 'newName', "Failed to copy file with new name");
|
||||
// check that the old file is still there
|
||||
try {
|
||||
await puter.fs.read(randName);
|
||||
pass("testFSCopyFileWithNewName passed");
|
||||
} catch (error) {
|
||||
fail("testFSCopyFileWithNewName failed to keep old file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSCopyFileWithNewName failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSStat = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.write(randName, 'testValue');
|
||||
let result = await puter.fs.stat(randName);
|
||||
assert(result.uid, "Failed to stat file");
|
||||
pass("testFSStat passed");
|
||||
} catch (error) {
|
||||
fail("testFSStat failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSStatDir = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.mkdir(randName);
|
||||
let result = await puter.fs.stat(randName);
|
||||
assert(result.uid, "Failed to stat directory");
|
||||
pass("testFSStatDir passed");
|
||||
} catch (error) {
|
||||
fail("testFSStatDir failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testFSStatNonExistent = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let result = await puter.fs.stat(randName);
|
||||
fail("testFSStatNonExistent failed");
|
||||
} catch (error) {
|
||||
if(error.code !== "subject_does_not_exist")
|
||||
fail("testFSStatNonExistent failed:", error);
|
||||
else
|
||||
pass("testFSStatNonExistent passed");
|
||||
}
|
||||
},
|
||||
|
||||
// create a directory, write a number of files to it, then delete it
|
||||
testFSDeleteDirWithFiles = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.mkdir(randName);
|
||||
await puter.fs.write(randName + '/file1', 'testValue');
|
||||
await puter.fs.write(randName + '/file2', 'testValue');
|
||||
await puter.fs.write(randName + '/file3', 'testValue');
|
||||
const result = await puter.fs.delete(randName, { recursive: true });
|
||||
assert(!result.uid, "Failed to delete directory");
|
||||
pass("testFSDeleteDirWithFiles passed");
|
||||
} catch (error) {
|
||||
fail("testFSDeleteDirWithFiles failed:", error);
|
||||
}
|
||||
},
|
||||
// check if stat on a directory returns name, path, is_dir
|
||||
testFSStatDirReturnsAttrs = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
await puter.fs.mkdir(randName);
|
||||
let result = await puter.fs.stat(randName);
|
||||
assert(result.name && typeof result.name === 'string', "Failed to stat directory (name)");
|
||||
assert(result.path && typeof result.path === 'string', "Failed to stat directory (path)");
|
||||
assert(result.immutable !== undefined, "Failed to stat directory (immutable)");
|
||||
assert(result.metadata !== undefined, "Failed to stat directory (metadata)");
|
||||
assert(result.modified !== undefined, "Failed to stat directory (modified)");
|
||||
assert(result.created !== undefined, "Failed to stat directory (created)");
|
||||
assert(result.accessed !== undefined, "Failed to stat directory (accessed)");
|
||||
assert(result.size !== undefined, "Failed to stat directory (size)");
|
||||
assert(result.layout !== undefined, "Failed to stat directory (layout)");
|
||||
assert(result.owner !== undefined && typeof result.owner === 'object', "Failed to stat directory (owner)");
|
||||
assert(result.dirname !== undefined && typeof result.dirname === 'string', "Failed to stat directory (dirname)");
|
||||
assert(result.parent_id !== undefined && typeof result.parent_id === 'string', "Failed to stat directory (parent_id)");
|
||||
// todo this will fail for now until is_dir is turned into boolean
|
||||
assert(result.is_dir !== undefined && typeof result.is_dir === 'boolean' && result.is_dir === true, "Failed to stat directory (is_dir)");
|
||||
assert(result.is_empty !== undefined && typeof result.is_empty === 'boolean', "Failed to stat directory (is_empty)");
|
||||
pass("testFSStatDirReturnsAttrs passed");
|
||||
} catch (error) {
|
||||
throw("testFSStatDirReturnsAttrs failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// test read() with the object returned by write()
|
||||
testFSReadWithWriteResult = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let writeResult = await puter.fs.write(randName, 'testValue');
|
||||
let result = await (await puter.fs.read(writeResult)).text();
|
||||
assert(result === 'testValue', "Failed to read from file");
|
||||
pass("testFSReadWithWriteResult passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSReadWithWriteResult failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSReadWithWriteResult failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// test stat() with the object returned by write()
|
||||
testFSStatWithWriteResult = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
let writeResult = await puter.fs.write(randName, 'testValue');
|
||||
let result = await puter.fs.stat(writeResult);
|
||||
assert(result.uid, "Failed to stat file");
|
||||
pass("testFSStatWithWriteResult passed");
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(randName);
|
||||
} catch (error) {
|
||||
fail("testFSStatWithWriteResult failed to delete file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
fail("testFSStatWithWriteResult failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// test creating files with names from naughtyStrings
|
||||
testFSWriteWithNaughtyStrings = async ()=>{
|
||||
try {
|
||||
let randName = puter.randName();
|
||||
for(let i = 0; i < naughtyStrings.length; i++) {
|
||||
let filename = randName + naughtyStrings[i];
|
||||
console.log(filename);
|
||||
let result = await puter.fs.write(filename, 'testValue');
|
||||
assert(result.uid, "Failed to write to file");
|
||||
// check name
|
||||
assert(result.name === filename, "Failed to write to file with naughty name: " + filename);
|
||||
// delete the file
|
||||
try {
|
||||
await puter.fs.delete(filename);
|
||||
} catch (error) {
|
||||
fail("testFSWriteWithNaughtyStrings failed to delete file: " + filename, error);
|
||||
}
|
||||
}
|
||||
pass("testFSWriteWithNaughtyStrings passed");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
fail("testFSWriteWithNaughtyStrings failed:", error);
|
||||
}
|
||||
},
|
||||
];
|
417
packages/puter-dot-js/test/kv.test.js
Normal file
417
packages/puter-dot-js/test/kv.test.js
Normal file
@ -0,0 +1,417 @@
|
||||
window.kvTests = [
|
||||
testSetKeyWithValue = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('testKey', 'testValue');
|
||||
assert(result === true, "Failed to set key with value");
|
||||
pass("testSetKeyWithValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetKeyWithValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testUpdateKey = async function() {
|
||||
try {
|
||||
await puter.kv.set('updateKey', 'initialValue');
|
||||
const result = await puter.kv.set('updateKey', 'updatedValue');
|
||||
assert(result === true, "Failed to update existing key");
|
||||
pass("testUpdateKey passed");
|
||||
} catch (error) {
|
||||
fail("testUpdateKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testKeySizeLimit = async function() {
|
||||
try {
|
||||
const largeKey = 'a'.repeat(1025); // 1 KB + 1 byte
|
||||
await puter.kv.set(largeKey, 'value');
|
||||
fail("testKeySizeLimit failed: No error thrown for large key");
|
||||
} catch (error) {
|
||||
pass("testKeySizeLimit passed:", error.message);
|
||||
}
|
||||
},
|
||||
|
||||
testInvalidParameters = async function() {
|
||||
try {
|
||||
await puter.kv.set(undefined, 'value');
|
||||
fail("testInvalidParameters failed: No error thrown for undefined key");
|
||||
} catch (error) {
|
||||
pass("testInvalidParameters passed:", error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// testEmptyKey should fail
|
||||
testEmptyKey = async function() {
|
||||
try {
|
||||
await puter.kv.set('', 'value');
|
||||
fail("testEmptyKey failed: No error thrown for empty key");
|
||||
} catch (error) {
|
||||
pass("testEmptyKey passed:", error.message);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
testSetNullValue = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('nullValueKey', null);
|
||||
assert(result === true, "Failed to set null value");
|
||||
pass("testSetNullValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetNullValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetObjectValue = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('objectKey', { a: 1 });
|
||||
assert(result === true, "Failed to set object as value");
|
||||
pass("testSetObjectValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetObjectValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetKeyWithSpecialCharacters = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('special@Key#', 'value');
|
||||
assert(result === true, "Failed to set key with special characters");
|
||||
pass("testSetKeyWithSpecialCharacters passed");
|
||||
} catch (error) {
|
||||
fail("testSetKeyWithSpecialCharacters failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetLargeValue = async function() {
|
||||
try {
|
||||
const largeValue = 'a'.repeat(10000); // 10 KB
|
||||
const result = await puter.kv.set('largeValueKey', largeValue);
|
||||
assert(result === true, "Failed to set large value");
|
||||
pass("testSetLargeValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetLargeValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetBooleanValue = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('booleanKey', true);
|
||||
assert(result === true, "Failed to set boolean value");
|
||||
pass("testSetBooleanValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetBooleanValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetNumericKey = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set(123, 'value');
|
||||
assert(result === true, "Failed to set numeric key");
|
||||
pass("testSetNumericKey passed");
|
||||
} catch (error) {
|
||||
fail("testSetNumericKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetConcurrentKeys = async function() {
|
||||
try {
|
||||
const promises = [puter.kv.set('key1', 'value1'), puter.kv.set('key2', 'value2')];
|
||||
const results = await Promise.all(promises);
|
||||
assert(results.every(result => result === true), "Failed to set concurrent keys");
|
||||
pass("testSetConcurrentKeys passed");
|
||||
} catch (error) {
|
||||
fail("testSetConcurrentKeys failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetValueAndRetrieve = async function() {
|
||||
try {
|
||||
await puter.kv.set('retrieveKey', 'testValue');
|
||||
const value = await puter.kv.get('retrieveKey');
|
||||
assert(value === 'testValue', "Failed to retrieve correct value");
|
||||
pass("testSetValueAndRetrieve passed");
|
||||
} catch (error) {
|
||||
fail("testSetValueAndRetrieve failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testUpdateValueAndRetrieve = async function() {
|
||||
try {
|
||||
await puter.kv.set('updateKey', 'initialValue');
|
||||
await puter.kv.set('updateKey', 'updatedValue');
|
||||
const value = await puter.kv.get('updateKey');
|
||||
assert(value === 'updatedValue', "Failed to retrieve updated value");
|
||||
pass("testUpdateValueAndRetrieve passed");
|
||||
} catch (error) {
|
||||
fail("testUpdateValueAndRetrieve failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetNumericValueAndRetrieve = async function() {
|
||||
try {
|
||||
await puter.kv.set('numericKey', 123);
|
||||
const value = await puter.kv.get('numericKey');
|
||||
assert(value === 123, "Failed to retrieve numeric value");
|
||||
pass("testSetNumericValueAndRetrieve passed");
|
||||
} catch (error) {
|
||||
fail("testSetNumericValueAndRetrieve failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetBooleanValueAndRetrieve = async function() {
|
||||
try {
|
||||
await puter.kv.set('booleanKey', true);
|
||||
const value = await puter.kv.get('booleanKey');
|
||||
assert(value === true, "Failed to retrieve boolean value");
|
||||
pass("testSetBooleanValueAndRetrieve passed");
|
||||
} catch (error) {
|
||||
fail("testSetBooleanValueAndRetrieve failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
testSetAndDeleteKey = async function() {
|
||||
try {
|
||||
await puter.kv.set('deleteKey', 'value');
|
||||
const result = await puter.kv.del('deleteKey');
|
||||
assert(result === true, "Failed to delete key");
|
||||
pass("testSetAndDeleteKey passed");
|
||||
} catch (error) {
|
||||
fail("testSetAndDeleteKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// if key does not exist, get() should return null
|
||||
testGetNonexistentKey = async function() {
|
||||
try {
|
||||
const value = await puter.kv.get('nonexistentKey_102mk');
|
||||
assert(value === null, "Failed to return `null` for nonexistent key");
|
||||
pass("testGetNonexistentKey passed");
|
||||
} catch (error) {
|
||||
fail("testGetNonexistentKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// string key and object value
|
||||
testSetObjectValue = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('objectKey', { a: 1 });
|
||||
assert(result === true, "Failed to set object as value");
|
||||
const value = await puter.kv.get('objectKey');
|
||||
assert(value.a === 1, "Failed to retrieve object value");
|
||||
pass("testSetObjectValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetObjectValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// string key and array value
|
||||
testSetArrayValue = async function() {
|
||||
try {
|
||||
const result = await puter.kv.set('arrayKey', [1, 2, 3]);
|
||||
assert(result === true, "Failed to set array as value");
|
||||
const value = await puter.kv.get('arrayKey');
|
||||
assert(value[0] === 1, "Failed to retrieve array value");
|
||||
pass("testSetArrayValue passed");
|
||||
} catch (error) {
|
||||
fail("testSetArrayValue failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testSetKeyWithSpecialCharactersAndRetrieve = async function() {
|
||||
try {
|
||||
await puter.kv.set('special@Key#', 'value');
|
||||
const value = await puter.kv.get('special@Key#');
|
||||
assert(value === 'value', "Failed to retrieve value for key with special characters");
|
||||
pass("testSetKeyWithSpecialCharactersAndRetrieve passed");
|
||||
} catch (error) {
|
||||
fail("testSetKeyWithSpecialCharactersAndRetrieve failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
testConcurrentSetOperations = async function() {
|
||||
try {
|
||||
const promises = [puter.kv.set('key1', 'value1'), puter.kv.set('key2', 'value2')];
|
||||
const results = await Promise.all(promises);
|
||||
assert(results.every(result => result === true), "Failed to set concurrent keys");
|
||||
pass("testConcurrentSetOperations passed");
|
||||
} catch (error) {
|
||||
fail("testConcurrentSetOperations failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
//test flush: create a bunch of keys, flush, then check if they exist
|
||||
testFlush = async function() {
|
||||
try {
|
||||
const keys = [];
|
||||
for(let i = 0; i < 10; i++){
|
||||
keys.push('key' + i);
|
||||
}
|
||||
await Promise.all(keys.map(key => puter.kv.set(key, 'value')));
|
||||
await puter.kv.flush();
|
||||
const results = await Promise.all(keys.map(key => puter.kv.get(key)));
|
||||
assert(results.every(result => result === null), "Failed to flush keys");
|
||||
pass("testFlush passed");
|
||||
} catch (error) {
|
||||
fail("testFlush failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// incr
|
||||
testIncr = async function() {
|
||||
try {
|
||||
const result = await puter.kv.incr('incrKey');
|
||||
assert(result === 1, "Failed to increment key");
|
||||
pass("testIncr passed");
|
||||
} catch (error) {
|
||||
fail("testIncr failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// decr
|
||||
testDecr = async function() {
|
||||
try {
|
||||
const result = await puter.kv.decr('decrKey');
|
||||
assert(result === -1, "Failed to decrement key");
|
||||
pass("testDecr passed");
|
||||
} catch (error) {
|
||||
fail("testDecr failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// incr existing key
|
||||
testIncrExistingKey = async function() {
|
||||
try {
|
||||
await puter.kv.set('incrKey', 1);
|
||||
const result = await puter.kv.incr('incrKey');
|
||||
assert(result === 2, "Failed to increment existing key");
|
||||
pass("testIncrExistingKey passed");
|
||||
} catch (error) {
|
||||
fail("testIncrExistingKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// decr existing key
|
||||
testIncrExistingKey = async function() {
|
||||
try {
|
||||
await puter.kv.set('decrKey', 2);
|
||||
const result = await puter.kv.decr('decrKey');
|
||||
assert(result === 1, "Failed to decrement existing key");
|
||||
pass("testDecrExistingKey passed");
|
||||
} catch (error) {
|
||||
fail("testDecrExistingKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// incr by amount
|
||||
testIncrByAmount = async function() {
|
||||
try {
|
||||
await puter.kv.set('incrKey', 1);
|
||||
const result = await puter.kv.incr('incrKey', 5);
|
||||
assert(result === 6, "Failed to increment key by amount");
|
||||
pass("testIncrByAmount passed");
|
||||
} catch (error) {
|
||||
fail("testIncrByAmount failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// decr by amount
|
||||
testDecrByAmount = async function() {
|
||||
try {
|
||||
await puter.kv.set('decrKey', 10);
|
||||
const result = await puter.kv.decr('decrKey', 5);
|
||||
assert(result === 5, "Failed to decrement key by amount");
|
||||
pass("testDecrByAmount passed");
|
||||
} catch (error) {
|
||||
fail("testDecrByAmount failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// incr by amount existing key
|
||||
testIncrByAmountExistingKey = async function() {
|
||||
try {
|
||||
await puter.kv.set('incrKey', 1);
|
||||
const result = await puter.kv.incr('incrKey', 5);
|
||||
assert(result === 6, "Failed to increment existing key by amount");
|
||||
pass("testIncrByAmountExistingKey passed");
|
||||
} catch (error) {
|
||||
fail("testIncrByAmountExistingKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// decr by amount existing key
|
||||
testDecrByAmountExistingKey= async function() {
|
||||
try {
|
||||
await puter.kv.set('decrKey', 10);
|
||||
const result = await puter.kv.decr('decrKey', 5);
|
||||
assert(result === 5, "Failed to decrement existing key by amount");
|
||||
pass("testDecrByAmountExistingKey passed");
|
||||
} catch (error) {
|
||||
fail("testDecrByAmountExistingKey failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// incr by negative amount
|
||||
testIncrByNegativeAmount = async function() {
|
||||
try {
|
||||
await puter.kv.set('incrKey', 1);
|
||||
const result = await puter.kv.incr('incrKey', -5);
|
||||
assert(result === -4, "Failed to increment key by negative amount");
|
||||
pass("testIncrByNegativeAmount passed");
|
||||
} catch (error) {
|
||||
fail("testIncrByNegativeAmount failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// decr by negative amount
|
||||
testDecrByNegativeAmount = async function() {
|
||||
try {
|
||||
await puter.kv.set('decrKey', 10);
|
||||
const result = await puter.kv.decr('decrKey', -5);
|
||||
assert(result === 15, "Failed to decrement key by negative amount");
|
||||
pass("testDecrByNegativeAmount passed");
|
||||
} catch (error) {
|
||||
fail("testDecrByNegativeAmount failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// list keys
|
||||
testListKeys = async function() {
|
||||
try {
|
||||
const keys = [];
|
||||
// flush first
|
||||
await puter.kv.flush();
|
||||
// create 10 keys
|
||||
for(let i = 0; i < 10; i++){
|
||||
keys.push('key' + i);
|
||||
}
|
||||
// set all keys
|
||||
await Promise.all(keys.map(key => puter.kv.set(key, 'value')));
|
||||
// list keys
|
||||
const result = await puter.kv.list();
|
||||
assert(result.length === 10, "Failed to list keys");
|
||||
pass("testListKeys passed");
|
||||
} catch (error) {
|
||||
fail("testListKeys failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// list keys using glob
|
||||
testListKeysGlob = async function() {
|
||||
try {
|
||||
const keys = [];
|
||||
// flush first
|
||||
await puter.kv.flush();
|
||||
// create 10 keys
|
||||
for(let i = 0; i < 10; i++){
|
||||
keys.push('key' + i);
|
||||
}
|
||||
// set all keys
|
||||
await Promise.all(keys.map(key => puter.kv.set(key, 'value')));
|
||||
// list keys
|
||||
const result = await puter.kv.list('k*');
|
||||
assert(result.length === 10, "Failed to list keys using glob");
|
||||
pass("testListKeysGlob passed");
|
||||
} catch (error) {
|
||||
fail("testListKeysGlob failed:", error);
|
||||
}
|
||||
},
|
||||
]
|
137
packages/puter-dot-js/test/run.html
Normal file
137
packages/puter-dot-js/test/run.html
Normal file
@ -0,0 +1,137 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../dist/puter.dev.js"></script>
|
||||
<script src="./kv.test.js"></script>
|
||||
<script src="./fs.test.js"></script>
|
||||
<style>
|
||||
#tests {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#run-tests {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #4c84af;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#unselect-all {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#select-all {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.test-container{
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
window.pass = function(msg) {
|
||||
// $('#tests').append(`<p style="color:green;">${msg}</p>`);
|
||||
}
|
||||
|
||||
window.fail = function(msg) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// print the test name with checkbox for each test
|
||||
$('#tests').append('<h2>File System Tests</h2>');
|
||||
for (let i = 0; i < fsTests.length; i++) {
|
||||
$('#tests').append(`<div class="test-container" id="fsTests-container-${i}">
|
||||
<input type="checkbox" class="test-checkbox" id="fsTests${i}" checked>
|
||||
<label for="fsTests${i}">${fsTests[i].name}</label><br>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
$('#tests').append('<h2>Key Value Tests</h2>');
|
||||
for (let i = 0; i < kvTests.length; i++) {
|
||||
$('#tests').append(`<div class="test-container" id="kvTests-container-${i}">
|
||||
<input type="checkbox" class="test-checkbox" id="kvTests${i}" checked>
|
||||
<label for="kvTests${i}">${kvTests[i].name}</label><br>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
window.assert = function(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message || "Assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
// go through fsTests and run each test
|
||||
for (let i = 0; i < fsTests.length; i++) {
|
||||
if (document.getElementById(`fsTests${i}`).checked) {
|
||||
try{
|
||||
await fsTests[i]();
|
||||
// make this test's container green
|
||||
$(`#fsTests-container-${i}`).css('background-color', '#85e085');
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
// make this test's container red
|
||||
$(`#fsTests-container-${i}`).css('background-color', '#ffbfbf');
|
||||
// message
|
||||
$(`#fsTests-container-${i}`).append(`<p style="color:#c00000;">${e}</p>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < kvTests.length; i++) {
|
||||
if (document.getElementById(`kvTests${i}`).checked) {
|
||||
try{
|
||||
await kvTests[i]();
|
||||
// make this test's container green
|
||||
$(`#kvTests-container-${i}`).css('background-color', '#85e085');
|
||||
|
||||
} catch (e) {
|
||||
// make this test's container red
|
||||
$(`#kvTests-container-${i}`).css('background-color', '#ff8484');
|
||||
// message
|
||||
$(`#kvTests-container-${i}`).append(`<p style="color:red;">${e}</p>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('#run-tests').click(() => {
|
||||
runTests();
|
||||
});
|
||||
|
||||
$('#unselect-all').click(() => {
|
||||
for (let i = 0; i < fsTests.length; i++) {
|
||||
$('.test-checkbox').prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
||||
$('#select-all').click(() => {
|
||||
for (let i = 0; i < fsTests.length; i++) {
|
||||
$('.test-checkbox').prop('checked', true);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav style="position: fixed; top: 0; width: 100%; background: #EEE; left: 0; padding-left: 10px;">
|
||||
<button id="run-tests">Run Tests</button>
|
||||
<span id="select-all">Select All</span>
|
||||
<span id="unselect-all">Unselect All</span>
|
||||
</nav>
|
||||
<div id="tests" style="margin-top:100px;"></div>
|
||||
</body>
|
||||
</html>
|
@ -53,11 +53,9 @@ window.gui = async function(options){
|
||||
window.max_item_name_length = options.max_item_name_length ?? 500;
|
||||
window.require_email_verification_to_publish_website = options.require_email_verification_to_publish_website ?? true;
|
||||
|
||||
// Add Puter.JS
|
||||
await loadScript('https://js.puter.com/v2/');
|
||||
|
||||
// DEV: Load the initgui.js file if we are in development mode
|
||||
if(!window.gui_env || window.gui_env === "dev"){
|
||||
await loadScript('/sdk/puter.dev.js');
|
||||
await loadScript('/initgui.js', {isModule: true});
|
||||
}
|
||||
|
||||
@ -65,6 +63,7 @@ window.gui = async function(options){
|
||||
// note: the order of the bundles is important
|
||||
// note: Build script will prepend `window.gui_env="prod"` to the top of the file
|
||||
else if(gui_env === "prod"){
|
||||
await loadScript('https://js.puter.com/v2/');
|
||||
// Load the minified bundles
|
||||
await loadCSS('/dist/bundle.min.css');
|
||||
await loadScript('/dist/bundle.min.js');
|
||||
|
Loading…
Reference in New Issue
Block a user