diff --git a/README.md b/README.md index 359e279..f903896 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,12 @@ An open, seamless XR web built from simple URLs and directories, without gatekee -XRForge transforms standard local or remote file directories into interconnected 3D spaces. -
Users can seamlessly "surf" the spatial web, turning everyday directories into 3D portals. +* turn everyday directories into 3D portals. +* seamlessly "surf" interconnected spatial webs on XR headsets +* transforms local/remote filesystems into interconnected 3D spaces. +* 3D (hyperlink) file browser for AR/VR +* automatic portal system, recursive symlinks supported +* infinite horizontally scaling ecosystems thanks to URLs (not databases) ``` ┌───────────────────────────────────┐ ┌──────────────────────────────────────────┐ @@ -56,7 +60,7 @@ Usage: ./xrforge.com [opts] Extract and run example 3D content in your browser: ```bash -$ unzip xrforge.com myverse/* # extract example 3D verse +$ unzip xrforge.com myverse/* # extract example 3D verse files $ ./xrforge.com server # run as server opening your browser at https://localhost:8080 ``` @@ -149,7 +153,7 @@ page css Optionally you can re-distribute `xrforge.com` with your modifications by running `zip xrforge.com page` -## Distribute your own XRForge +## Distribute: your own executable You can preload the `xrforge.com` binary with your own experiences as a single-executable-file: @@ -160,18 +164,21 @@ $ cp xrforge.com myproject.com > Profit! -## Plugins +## Distribute: static website -xrforge is basically plugins working together.
-You can create your own plugins by running: +Generate a static website: -```bash -$ ./xrforge.com createplugin > xrforge.myplugin.lua +``` +$ ./xrforge.com generate baseurl=/mysubfolder/ +✅ finished generating 'www' folder ``` -Look in the [src/plugin](src/plugin) directory for examples +A convenient way is forking [this github repo](https://github.com/coderofsalvation/xrforge-website-example) +to automatically obtain a xrforge-generated github page. -## Run as server +> NOTE: passing extra flags (run `xrforge.com help` for all flags) is important when running the website in a subfolder (`baseurl=...` e.g.) + +## Distribute: selfhosting on a server BAREMETAL: @@ -188,16 +195,17 @@ $ docker run -d -p 8080:8080 -w /app -v $(pwd):/app busybox /app/xrforge.com ser ``` +## Plugins -## Scan manyfold +xrforge is basically plugins working together.
+You can create your own plugins by running: -------------- -``` -manual scan (via cli?) -curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=name' -H 'accept: application/vnd.manyfold.v0+json' -curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=recent' -H 'accept: application/vnd.manyfold.v0+json' +```bash +$ ./xrforge.com createplugin > xrforge.myplugin.lua ``` +Look in the [src/plugin](src/plugin) directory for examples + ## Developing ```bash @@ -216,6 +224,8 @@ $ DEBUG=1 result/bin/xrforge.com Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan binary) +> **NOTE**: this is a **static webgenerator-first**-and-server-**last** project, hence it is restricted to [GET requests](src/middleware/onlyGET.lua) (unless authenticated as admin). Going beyond this paradigm is considered unsafe and out of scope. + ## Permacomputing Spirit * developed on [Dillo](https://dillo-browser.github.io/) as lowest common denominator / compatibility @@ -223,7 +233,7 @@ Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan bina * gracefully degrades to admin/filebrowser for >noscript>/potato-browsers * server is using lua-scripting via cosmopolitan libc's [redbean](https://redbean.dev) -> So yes, the codebase/html/css is intentionally 90-ish: that's **exactly why it works** on potato-devices AND modern devices. +> So yes, the codebase/html/css is intentionally 90-ish: that's **exactly why it gracefully degrades** on potato-devices. ## Credits / Made with @@ -243,6 +253,16 @@ Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan bina NOTES: +## Scan manyfold + +------------- +``` +manual scan (via cli?) +curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=name' -H 'accept: application/vnd.manyfold.v0+json' +curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=recent' -H 'accept: application/vnd.manyfold.v0+json' +``` + + ``` /admin.lua <--- define other instances..for CORS allow / mount http diff --git a/src/.init.lua b/src/.init.lua index b12054b..d64f89b 100644 --- a/src/.init.lua +++ b/src/.init.lua @@ -78,6 +78,7 @@ print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging fro -- init URL router app.url['^/data'] = '/data.lua' -- setup custom file endpoint +app.use( require('middleware/onlyGET')({exclude={'^/admin'}}) ) -- we are staticwebgen-first server-last app.use( app.plugin.global.middleware.homepage_2D_or_3D ) app.url['^/admin[/]?$'] = '/page/admin/index.lua' diff --git a/src/.lua/config.lua b/src/.lua/config.lua index df9874f..2749b1c 100644 --- a/src/.lua/config.lua +++ b/src/.lua/config.lua @@ -5,6 +5,7 @@ return function(app) local config = { -- default name = "XRForge", baseurl="/", + host="localhost", admin_title = "admin panel", admin_subtitle = "Manage your XR experiences here", admin_user = "admin", diff --git a/src/.lua/soakbean.lua b/src/.lua/soakbean.lua index d9dfc5d..c791513 100644 --- a/src/.lua/soakbean.lua +++ b/src/.lua/soakbean.lua @@ -87,12 +87,23 @@ sb = { tpl = function(s, __data) local __env = {} + local fallback = function(key) + if os.getenv('DEBUG') then + print("⚠️ template '" .. key .. "' does not exist") + end + return setmetatable({}, { + __tostring = function() return "" end, + __concat = function(a, b) return tostring(a) .. tostring(b) end, + __call = function(...) return "" end + }) + end setmetatable(__env, { - __index = function(t, key) return __data[key] or _G[key] end, + __index = function(t, key) return __data[key] or _G[key] or fallback(key) end, __newindex = function(t, key, value) __data[key] = value end }) -- lets evaluate inline lua linearly local luastr = 'local str=""\n print = function(s) str = str .. s .. "\\n" end\n' + luastr = luastr .. 'defined = function(v) return tostring(v or "") ~= "" end\n' local islua = false for str in string.gmatch(s, "([^\n]+)") do if str:match("{if ") or str:match("{for ") then @@ -196,7 +207,10 @@ sb = { local res={ _status=nil, _header={}, - _body="" + _body="", + send = function(req,res,next) + sb.response()(req,res,next) + end } local params = internal and {} or GetParams() if params ~= nil then @@ -251,7 +265,9 @@ sb = { if type(res._body) == "string" then Write( res._body ) else print("[ERROR] res.body is not a string (HINT: use json middleware)") end - else next() end + else + if next then next() end + end end end, @@ -311,6 +327,7 @@ sb = { return function(data) local app = {} setmetatable(app,sb) + sb.arg = arg sb.bin = arg[-1]:gsub(".*/","") sb.bindir = arg[-1]:gsub("/[a-zA-Z0-9-_ ]+.com$","/") sb.app = app diff --git a/src/.lua/util.lua b/src/.lua/util.lua index 47b1735..2ef7e9e 100644 --- a/src/.lua/util.lua +++ b/src/.lua/util.lua @@ -75,6 +75,12 @@ util.templateFile = function(file,data) return util.template( LoadAsset(file), data) end +util.baseURLify = function(url) + if url:match("^" .. app.opts.baseurl) then return url end -- only do once + if url:match("://") then return url end + return url:gsub("^[/]?", app.opts.baseurl) +end + util.getURL = function(url,opts) opts = opts or {} local port = '' @@ -83,8 +89,9 @@ util.getURL = function(url,opts) return 'http://localhost:' .. app.port .. url else port = ':' .. GetPort() - if port == 80 then port = '' end - return GetScheme() .. '://' .. GetHost() .. port .. (url or GetPath()) + if port == ':80' then port = '' end + if arg[1] == 'generate' and port == ':8080' then port = '' end + return GetScheme() .. '://' .. (app.opts.host or GetHost()) .. port .. (url or GetPath()) end end @@ -92,8 +99,8 @@ util.template = function(content,data) local mdata = util.merge(data or app,{}) mdata = util.merge(mdata, {opts = app.opts}) -- set fallback vars - mdata.urlpath = GetPath() - mdata.url = mdata.url or util.getURL() + mdata.urlpath = util.baseURLify( GetPath() ) + mdata.url = mdata.url or mdata.urlpath mdata.title = mdata.title or app.opts.subtitle mdata.header_include = mdata.header_include or '' mdata.footerlinks = {} @@ -330,6 +337,13 @@ function util.stripPath(str) return str:gsub("^/zip/",""):gsub("^./",""):gsub("^/",""):gsub("/$","") end +function util.truncate(str,maxchar,filler) + filler = filler or '..' + str = str:gsub(".*://",""):gsub("/$",""):gsub("/.*/","/.../") + if maxchar ~= nil and #str > maxchar then str = str:sub(0,maxchar) .. filler end + return str +end + -- WARNING: this is a destructive function which renders the redbean server inactive -- only to be used in tests or staticwebgen plugin util.queueServerRequest = function(method, path, params, host, headers, body, scheme, port) diff --git a/src/css/xrforge.css b/src/css/xrforge.css index 7931540..4e4feef 100644 --- a/src/css/xrforge.css +++ b/src/css/xrforge.css @@ -40,6 +40,7 @@ body,html { } body{ + overflow-wrap: break-word; background:#000; margin:20px; } diff --git a/src/middleware/onlyGET.lua b/src/middleware/onlyGET.lua new file mode 100644 index 0000000..6194010 --- /dev/null +++ b/src/middleware/onlyGET.lua @@ -0,0 +1,24 @@ +-- soakbean middleware function +return function(opts) + opts = opts or {exclude={"^/admin"}} + return function(req,res,next) + local pass = false + if req.method == "GET" then + pass = true + else + foreach(opts.exclude, function(k,path) + if req.url:match(path) then + pass = true + end + end) + end + if pass then + next() + else + res.status(405) + res.body("not allowed") + res.send(req,res) + end + end +end + diff --git a/src/page/dir.html b/src/page/dir.html index c9ec6f4..2524b9f 100644 --- a/src/page/dir.html +++ b/src/page/dir.html @@ -19,6 +19,18 @@ end}
{if items then + + function getURL(url) + if app.arg[1] == 'generate' and url:match("://") then + url = 'janusweb/index.html#janus.url=' .. + EscapeFragment( url ) .. '&janus.referrer=' .. util.getURL(urlpath ) + url = util.baseURLify(url) + else + url = util.stripPath( url ) + end + return url + end + for i=1,#items do local item = items[i] local visible = item.view2D == nil or (item.view2D ~= nil and item.view2D ~= false) @@ -29,10 +41,10 @@ end} if item.view3D or item.portal3D == false then icon = 'play' end print([[ @@ -49,10 +61,10 @@ end} if item.view3D or item.portal3D == false then icon = 'play' end print([[ diff --git a/src/page/dir.lua b/src/page/dir.lua index 186dd20..b01c83d 100644 --- a/src/page/dir.lua +++ b/src/page/dir.lua @@ -13,11 +13,12 @@ return { local isdir = urlpath:sub(-1) == "/" local nslashes = util.countChar('/',urlpath) local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end - local urlhash = urlpath .. util.dump( req.param ) + local urlhash = urlpath .. util.dump( req.param ) + local referrer = util.baseURLify( (isroot and not req.param.url) and "" or urlpath ) if (isroot or urlpath:match("^"..rootpath)) and not urlpath:gsub(".*/",""):match("%.") -- no regular file with extension - and not req.param.url then + then local redirect = false if not app.cache.url[ urlhash ] or util.count(req.params) > 0 then @@ -45,14 +46,10 @@ return { res.items[ v.id ] = v res.parent = parent end - if not isroot then - res.items[ v.parent.id ] = { - view2D = false, - url = v.parent.parent.url, - title = app.opts.plugin_janusxr_portal_up, - name = app.opts.plugin_janusxr_portal_up, - thumbnail = '', - } + if isroot then + referrer = app.opts.plugin_federation_parent_url + else + referrer = v.parent.parent.url end end end @@ -62,7 +59,8 @@ return { local data = util.merge({ url = req.param.url, -- query hint to render janus.url in iframe (index.html) items = {}, - referrer = isroot and app.opts.plugin_federation_parent_url or util.getURL(), + isroot = isroot, + referrer = referrer, parent=res.parent, template=res.template },{ opts = app.opts }) @@ -78,7 +76,7 @@ return { -- prioritize markup experience above binary 3D files as experience containers if not data.item or (isMarkupExperience(v) and not isMarkupExperience(data.item)) then -- update referrer url - data.referrer = util.getURL( v.view3D and v.parent.parent.url or v.parent.url ) + data.referrer = util.baseURLify(v.view3D and v.parent.parent.url or v.parent.url) data.item = v data.title = data.item.title data.parent = v.parent diff --git a/src/page/header.html b/src/page/header.html index 697194c..a98159f 100644 --- a/src/page/header.html +++ b/src/page/header.html @@ -21,7 +21,7 @@