Sign in to T5DE's hub from Settings

Settings > T5DE gets a T5DE Hub section to sign in (showing the code and opening the hub's page) or out,
through T5DE's proxy, which keeps the sign-in.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-29 19:25:49 -07:00
1 parent 959a454007
commit 1a2c85126e
2 files changed
+166 -1

No files matched your search

+5 -1
View File
@@ -89,7 +89,11 @@ modern stack.
The proxy also serves community products from local packs (`%APPDATA%\T5DE\packs`, `T5DE_PACKS`; t5de-proxy's
README, t5de-api's `docs/pack.md`); the client needs no patch for it. To test one: `drive.py url
"imvu:PutOn?productId=<local ID>"` (local IDs from 2,000,000,000, in `%APPDATA%\T5DE\proxy\products.json`), and
Dress Up for outfit saves.
Dress Up for outfit saves. The proxy also gets T5 products from the hub's catalogs into the person's library
and the client's inventory, and answers `http://t5de-proxy.local/` (its own control address, needing its token):
the `t5de-hub` patch's Settings > T5DE > T5DE Hub section signs in and out through it
(`SettingsGeckoListener.handle_t5deHub`). `t5de build` installs the pinned proxy; to test an unreleased one, copy
t5de-proxy's `dist/t5de-proxy.exe` into `build/IMVUClient/` after building.
## Verified facts (client 554.8)
@@ -0,0 +1,161 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: dhkatz <[email protected]>
Date: Thu, 29 Oct 2026 19:20:16 -0700
Subject: [PATCH] Sign in to T5DE's hub from Settings
Settings > T5DE gets a T5DE Hub section: sign in (it shows a code and opens the hub's page for it in the
browser) or out, and who's signed in. T5DE's proxy keeps the sign-in and answers the page at
http://t5de-proxy.local/; signing in or out takes effect at once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
T5de-Id: t5de-hub
T5de-Name: T5DE Hub
T5de-Group: general
T5de-Depends: t5de-proxy, t5de-renderer
---
imvuContent/settings/index.html | 12 ++++++++
imvuContent/settings/style.css | 9 ++++++
imvuContent/settings/t5de-hub.js | 47 +++++++++++++++++++++++++++++++
library/imvu/mode/SettingsMode.py | 20 +++++++++++++
4 files changed, 88 insertions(+)
create mode 100644 imvuContent/settings/t5de-hub.js
diff --git a/imvuContent/settings/index.html b/imvuContent/settings/index.html
index 72afd50a1c2ac86ced5d3df19db690df0e82bad4..045ac3e815fcc62e987b119003f7009d7c680ed0 100644
--- a/imvuContent/settings/index.html
+++ b/imvuContent/settings/index.html
@@ -29,6 +29,7 @@
<script src="BlockActionSettings.js"></script>
<script src="t5de.js"></script>
<script src="t5de-graphics.js"></script>
+ <script src="t5de-hub.js"></script>
<script src="../widgets/button/imvuButton.js"></script>
</head><body class="yui-skin-sam">
@@ -121,6 +122,16 @@
</div>
</div>
<div class="page" imvu:page="t5de" id="t5de-page">
+ <div class="subsection" id="t5de-hub">
+ <h1>T5DE Hub</h1>
+ <div id="t5de-hub-status">&nbsp;</div>
+ <div id="t5de-hub-code" class="hidden"></div>
+ <div class="t5de-hub-buttons">
+ <button id="t5de-hub-signin">Sign In</button>
+ <button id="t5de-hub-signout" class="hidden">Sign Out</button>
+ </div>
+ <div class="info t5de-info">Your T5DE account: community products, your library, and who's online.</div>
+ </div>
<div class="subsection" id="t5de-graphics">
<h1>Graphics</h1>
<div><span class="choice_desc">Frame Rate</span>
@@ -249,6 +260,7 @@
var tb = new ToolBar(imvu.call);
t5deSettings(imvu);
t5deGraphicsSettings(imvu);
+ t5deHubSettings(imvu);
}
</script>
</body></html>
\ No newline at end of file
diff --git a/imvuContent/settings/style.css b/imvuContent/settings/style.css
index 76187e882716ca5bcccf71952ba8657a739495c0..c846b5ef0772ff8b8fb5e1643bc45b55285bb984 100644
--- a/imvuContent/settings/style.css
+++ b/imvuContent/settings/style.css
@@ -480,3 +480,12 @@ ul li {
.t5de-info {
margin-top: 10px;
}
+#t5de-hub-code {
+ margin-top: 6px;
+ font-size: 16px;
+ font-weight: bold;
+ letter-spacing: 2px;
+}
+.t5de-hub-buttons {
+ margin-top: 8px;
+}
diff --git a/imvuContent/settings/t5de-hub.js b/imvuContent/settings/t5de-hub.js
new file mode 100644
index 0000000000000000000000000000000000000000..2cb97c2d76e8429a3cd36372a34e28324a731137
--- /dev/null
+++ b/imvuContent/settings/t5de-hub.js
@@ -0,0 +1,47 @@
+// Signing in to T5DE's hub (the T5DE Hub patch), through T5DE's proxy, which keeps the sign-in
+// (SettingsMode.py: handle_t5deHub). Signing in opens the hub's page in the browser for the code shown here.
+function t5deHubSettings(imvu) {
+ var status = document.getElementById('t5de-hub-status');
+ var code = document.getElementById('t5de-hub-code');
+ var signin = document.getElementById('t5de-hub-signin');
+ var signout = document.getElementById('t5de-hub-signout');
+ var timer = null;
+
+ function call(action) {
+ try {
+ return JSON.parse(imvu.call('t5deHub', action));
+ } catch (e) {
+ return {error: 'proxy'};
+ }
+ }
+
+ function show(s) {
+ $(code).addClass('hidden');
+ $(signin).toggleClass('hidden', !!s.signedIn);
+ $(signout).toggleClass('hidden', !s.signedIn);
+ if (s.error === 'proxy') {
+ status.textContent = "T5DE's proxy isn't running.";
+ $(signin).addClass('hidden');
+ } else if (s.signedIn) {
+ status.textContent = s.username ? 'Signed in as ' + s.username + '.' : 'Signed in.';
+ } else if (s.signingIn) {
+ status.textContent = s.code ? 'Enter this code on the page that opened:' : 'Getting a code...';
+ if (s.code) {
+ code.textContent = s.code.slice(0, 4) + '-' + s.code.slice(4);
+ $(code).removeClass('hidden');
+ }
+ } else if (s.error) {
+ status.textContent = "Couldn't sign in: " + s.error;
+ } else {
+ status.textContent = 'Not signed in.';
+ }
+ clearTimeout(timer);
+ if (s.signingIn || (s.signedIn && !s.username)) {
+ timer = setTimeout(function () { show(call('status')); }, 1500);
+ }
+ }
+
+ signin.addEventListener('click', function () { show(call('signin')); }, false);
+ signout.addEventListener('click', function () { show(call('signout')); }, false);
+ show(call('status'));
+}
diff --git a/library/imvu/mode/SettingsMode.py b/library/imvu/mode/SettingsMode.py
index 69a1ca1d6ca6ca2818c3d96cdd11c63939243c9f..27ef0499f542c9f7e558d94fcc82b34fdb98f185 100644
--- a/library/imvu/mode/SettingsMode.py
+++ b/library/imvu/mode/SettingsMode.py
@@ -65,6 +65,26 @@ class SettingsGeckoListener(object):
_winreg.SetValueEx(key, 'FrameRate', 0, _winreg.REG_DWORD, int(rate))
finally:
key.Close()
+
+ def handle_t5deHub(self, context, action):
+ """T5DE's hub sign-in, kept by T5DE's proxy, which answers at http://t5de-proxy.local/: action 'status',
+ 'signin' or 'signout'. The proxy's answer as JSON text, or {"error": "proxy"} if it isn't running."""
+ import httplib, json
+ from main.client import t5deProxy, t5deProxyAuthorization
+ proxy = t5deProxy()
+ if proxy is None:
+ return json.dumps({'error': 'proxy'})
+ host, port, token = proxy
+ method, path = {'status': ('GET', '/hub'), 'signin': ('POST', '/hub/signin'), 'signout': ('POST', '/hub/signout')}[action]
+ conn = httplib.HTTPConnection(host, port, timeout=10)
+ try:
+ conn.request(method, 'http://t5de-proxy.local' + path, '', {'Proxy-Authorization': t5deProxyAuthorization(token)})
+ response = conn.getresponse()
+ return response.read() if response.status == 200 else json.dumps({'error': 'proxy'})
+ except Exception:
+ return json.dumps({'error': 'proxy'})
+ finally:
+ conn.close()
def handle_registerSettingsListeners(self, context, prefList):
for pref in prefList:
self.__serviceProvider.eventBus.register(self.__serviceProvider.prefService, 'PrefChanged-%s' % (pref,), self.__prefListener)