Added support for new login/token endpoints.

This commit is contained in:
flash 2025-04-22 20:18:52 +00:00
parent 176db54a72
commit 4f3b3257b0
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
8 changed files with 95 additions and 81 deletions

31
src/mami.js/auth.js Normal file
View file

@ -0,0 +1,31 @@
const MamiIsAuth = value => {
return typeof value === 'object'
&& value !== null
&& 'type' in value
&& (value.type === null || typeof value.type === 'string')
&& 'token' in value
&& (value.token === null || typeof value.token === 'string')
&& 'header' in value
&& (value.header === null || typeof value.header === 'string')
&& 'refresh' in value
&& typeof value.refresh === 'function';
};
const MamiFlashiiAuth = function(flashii) {
let type = null;
let token = null;
return {
get type() { return type; },
get token() { return token; },
get header() { return type ? `${type} ${token}` : null; },
async refresh() {
try {
({ token_type: type=null, access_token: token=null } = await flashii.v1.chat.token());
} catch(ex) {
await flashii.v1.chat.login();
}
},
};
};

View file

@ -1,19 +1,15 @@
#include events.js
#include flashii.js
const MamiContext = function(globalEventTarget, eventTarget) {
const MamiContext = function(globalEventTarget, eventTarget, auth, flashii) {
if(typeof globalEventTarget !== 'object' && globalEventTarget === null)
throw 'globalEventTarget must be undefined or a non-null object';
if(eventTarget === undefined)
if(eventTarget === undefined || eventTarget === null)
eventTarget = 'mami';
else if(typeof eventTarget !== 'object' || eventTarget === null)
throw 'eventTarget must be a string or a non-null object';
if(typeof eventTarget === 'string')
eventTarget = globalEventTarget.scopeTo(eventTarget);
const flashii = new Flashii(`${window.FII_URL}/api`);
else if(typeof eventTarget !== 'object')
throw 'eventTarget must be a string or a non-null object';
let isUnloading = false;
@ -28,6 +24,8 @@ const MamiContext = function(globalEventTarget, eventTarget) {
return {
get globalEvents() { return globalEventTarget; },
get events() { return eventTarget; },
get auth() { return auth; },
get flashii() { return flashii; },
get isUnloading() { return isUnloading; },

View file

@ -1,15 +1,11 @@
const MamiIsEventTarget = value => {
if(typeof value !== 'object' || value === null)
return false;
if(typeof value.scopeTo !== 'function'
|| typeof value.create !== 'function'
|| typeof value.watch !== 'function'
|| typeof value.unwatch !== 'function'
|| typeof value.dispatch !== 'function')
return false;
return true;
return value !== null
&& typeof value === 'object'
&& typeof value.scopeTo === 'function'
&& typeof value.create === 'function'
&& typeof value.watch === 'function'
&& typeof value.unwatch === 'function'
&& typeof value.dispatch === 'function';
};
const MamiEventTargetScoped = function(eventTarget, prefix) {

View file

@ -45,6 +45,7 @@ const Flashii = function(baseUrl) {
body=null,
headers=null,
type='json',
authed=false,
}) => {
const url = createUrl(path, fields);
if(params)
@ -58,7 +59,7 @@ const Flashii = function(baseUrl) {
headers ??= {};
if(fresh) headers['Cache-Control'] = 'no-cache';
const options = { type, headers };
const options = { type, headers, authed };
return await $xhr.send(method, url, options, body);
};
@ -67,6 +68,34 @@ const Flashii = function(baseUrl) {
fii.v1 = {};
fii.v1.chat = {};
fii.v1.chat.login = function({ redirect=null, assign=true }) {
return new Promise(resolve => {
redirect ??= `${location.protocol}//${location.host}`;
if(typeof redirect !== 'string')
throw new Error('redirect must a string.');
const url = createUrl('/v1/chat/login');
url.searchParams.set('redirect', redirect);
// intentionally does not resolve
if(assign)
location.assign(url);
else
resolve(url);
});
};
fii.v1.chat.token = async function() {
const { status, body } = await send({ method: 'GET', path: '/v1/chat/token', authed: true, fresh: true });
if(status === 403)
throw new Error('You must be logged in to use chat.');
if(status > 299)
throw new Error(`Failed to fetch authorization token with error code ${status}.`);
return body;
};
const verifyColourPresetName = name => {
if(/^([^A-Za-z0-9\-_]+)$/gu.test(name))
throw new Error('name argument is not an acceptable colour preset name.');

View file

@ -7,6 +7,7 @@ window.Umi = { UI: {} };
#include animate.js
#include args.js
#include auth.js
#include awaitable.js
#include common.js
#include compat.js
@ -14,8 +15,8 @@ window.Umi = { UI: {} };
#include context.js
#include emotes.js
#include events.js
#include flashii.js
#include mobile.js
#include mszauth.js
#include parsing.js
#include themes.js
#include txtrigs.js
@ -57,9 +58,19 @@ const MamiInit = async args => {
define('parent').default(document.body).constraint(value => value instanceof Element).done();
define('eventTarget').required().constraint(MamiIsEventTarget).done();
define('settingsPrefix').default('umi-').done();
define('auth').default(null).constraint(MamiIsAuth).done();
});
const ctx = new MamiContext(args.eventTarget);
const flashii = new Flashii(`${window.FII_URL}/api`);
let auth = args.auth;
if(!auth) {
auth = new MamiFlashiiAuth(flashii);
await auth.refresh();
setInterval(() => { auth.refresh(); }, 600000);
}
const ctx = new MamiContext(args.eventTarget, null, auth, flashii);
// remove this later and replace with the one commented out way below
if(!('mami' in window))
@ -82,26 +93,6 @@ const MamiInit = async args => {
}
}
if(!MamiMisuzuAuth.hasInfo()) {
try {
const auth = await MamiMisuzuAuth.update();
if(!auth.ok)
throw 'Authentication failed.';
} catch(ex) {
console.error(ex);
location.assign(`${window.FII_URL}/_sockchat/login`);
return;
}
setInterval(() => {
MamiMisuzuAuth.update()
.then(auth => {
if(!auth.ok)
location.assign(`${window.FII_URL}/_sockchat/login`);
})
}, 600000);
}
const settings = new MamiSettings(args.settingsPrefix, ctx.events.scopeTo('settings'));
ctx.settings = settings;
@ -221,7 +212,7 @@ const MamiInit = async args => {
// loading these asynchronously makes them not show up in the backlog
// revisit when emote reparsing is implemented
try {
await MamiEmotes.loadApi(ctx.flashii);
await MamiEmotes.loadApi(flashii);
} catch(ex) {
console.error('Failed to load emoticons.', ex);
}
@ -300,7 +291,7 @@ const MamiInit = async args => {
onclick: ev => {
if(bbCode.tag === 'color') {
if(colourPicker === undefined) {
colourPicker = new MamiColourPicker({ flashii: ctx.flashii });
colourPicker = new MamiColourPicker({ flashii });
layout.element.appendChild(colourPicker.element);
}
@ -378,7 +369,7 @@ const MamiInit = async args => {
});
settings.watch('weeaboo', ev => {
if(ev.detail.value) Weeaboo.init(ctx.flashii);
if(ev.detail.value) Weeaboo.init(flashii);
});
settings.watch('osuKeysV2', ev => {
@ -562,7 +553,7 @@ const MamiInit = async args => {
button.disabled = true;
button.textContent = 'Reloading emoticons...';
try {
await MamiEmotes.loadApi(ctx.flashii, true);
await MamiEmotes.loadApi(flashii, true);
} finally {
button.textContent = textOrig;
button.disabled = false;

View file

@ -1,29 +0,0 @@
#include common.js
const MamiMisuzuAuth = (() => {
let userId = null;
let authMethod = 'Misuzu';
let authToken = null;
return {
hasInfo: () => userId !== null && authToken !== null,
getUserId: () => userId,
getAuthToken: () => authToken,
getLine: () => `${authMethod} ${authToken}`,
getInfo: () => {
return {
method: authMethod,
token: authToken,
};
},
update: async () => {
const { body } = await $xhr.get(`${window.FII_URL}/_sockchat/token`, { authed: true, type: 'json' });
if(body.ok) {
userId = body.usr.toString();
authToken = body.tkn;
}
return body;
},
};
})();

View file

@ -1,5 +1,4 @@
#include compat.js
#include mszauth.js
#include proto/sockchat/client.js
const MamiSockChat = function(eventTarget) {
@ -34,9 +33,8 @@ const MamiSockChat = function(eventTarget) {
return ex.wasKicked === true;
}
},
authenticate: async () => {
const authInfo = MamiMisuzuAuth.getInfo();
await client.sendAuth(authInfo.method, authInfo.token);
authenticate: async auth => {
await client.sendAuth(auth.type, auth.token);
},
setDumpPackets: state => {
dumpPackets = !!state;

View file

@ -42,7 +42,7 @@ const MamiSockChatHandlers = function(
handlers['conn:ready'] = ev => {
if(dumpEvents) console.log('conn:ready');
client.authenticate().then(() => {
client.authenticate(ctx.auth).then(() => {
if(!legacyUmiConnectFired) {
legacyUmiConnectFired = true;
ctx.globalEvents.dispatch('umi:connect');
@ -117,7 +117,7 @@ const MamiSockChatHandlers = function(
}
if(ev.detail.session.needsAuth) {
location.assign(`${window.FII_URL}/_sockchat/login`);
ctx.flashii.v1.chat.login();
return;
}