Spaces:
Build error
Build error
const formatMessage = require('format-message'); | |
const BlockType = require('../../extension-support/block-type'); | |
const ArgumentType = require('../../extension-support/argument-type'); | |
const Cast = require('../../util/cast'); | |
const Legacy = require('./legacy'); | |
const Icon = require("./icon.svg"); | |
/** | |
* Class for Scratch Authentication blocks | |
* @constructor | |
*/ | |
let currentPrivateCode = ''; | |
class JgScratchAuthenticateBlocks { | |
constructor(runtime) { | |
/** | |
* The runtime instantiating this block package. | |
* @type {Runtime} | |
*/ | |
this.runtime = runtime; | |
this.promptStatus = { | |
inProgress: false, | |
blocked: false, | |
completed: false, | |
userClosed: false, | |
}; | |
this.loginInfo = {}; | |
// legacy | |
this.keepAllowingAuthBlock = true; | |
this.disableConfirmationShown = false; | |
} | |
/** | |
* dummy function for reseting user provided permisions when a save is loaded | |
*/ | |
deserialize() { | |
this.disableConfirmationShown = false; | |
} | |
/** | |
* @returns {object} metadata for this extension and its blocks. | |
*/ | |
getInfo() { | |
return { | |
id: 'jgScratchAuthenticate', | |
name: 'Scratch Auth', | |
color1: '#FFA01C', | |
color2: '#ff8C00', | |
blockIconURI: Icon, | |
// TODO: docs doesnt exist, make some docs | |
// docsURI: 'https://docs.penguinmod.com/extensions/scratch-auth', | |
blocks: [ | |
// LEGACY BLOCK | |
{ | |
opcode: 'authenticate', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.authenticate', | |
default: 'get scratch username and set sign in location name to [NAME]', | |
description: "Block that returns the user's name on Scratch." | |
}), | |
disableMonitor: true, | |
hideFromPalette: true, | |
arguments: { | |
NAME: { type: ArgumentType.STRING, defaultValue: "PenguinMod" } | |
}, | |
blockType: BlockType.REPORTER | |
}, | |
// NEW BLOCKS | |
{ | |
opcode: 'showPrompt', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.showPrompt', | |
default: 'show login message as [NAME]', | |
description: "Block that shows the Log in menu from Scratch Authentication." | |
}), | |
arguments: { | |
NAME: { | |
type: ArgumentType.STRING, | |
menu: 'loginLocation' | |
} | |
}, | |
blockType: BlockType.COMMAND | |
}, | |
{ | |
opcode: 'getPromptStatus', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.promptStatus', | |
default: 'login prompt [STATUS]?', | |
description: "The status of the login prompt for Scratch Authentication." | |
}), | |
arguments: { | |
STATUS: { | |
type: ArgumentType.STRING, | |
menu: "promptStatus" | |
} | |
}, | |
disableMonitor: true, | |
blockType: BlockType.BOOLEAN | |
}, | |
{ | |
opcode: 'privateCode', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.privateCode', | |
default: 'authentication code', | |
description: "The login code when Scratch Authentication closes the login prompt." | |
}), | |
disableMonitor: true, | |
blockType: BlockType.REPORTER | |
}, | |
{ | |
opcode: 'serverRedirectLocation', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.serverRedirectLocation', | |
default: 'redirect location', | |
description: "The redirect location when Scratch Authentication closes the login prompt." | |
}), | |
disableMonitor: true, | |
blockType: BlockType.REPORTER | |
}, | |
'---', | |
{ | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.labels.loginInfo1', | |
default: 'The blocks below invalidate', | |
description: "Label to denote that blocks invalidate the Scratch Auth private code below this label" | |
}), | |
blockType: BlockType.LABEL | |
}, | |
{ | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.labels.loginInfo2', | |
default: 'the authentication code from above.', | |
description: "Label to denote that blocks invalidate the Scratch Auth private code below this label" | |
}), | |
blockType: BlockType.LABEL | |
}, | |
{ | |
opcode: 'validLogin', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.validLogin', | |
default: 'login is valid?', | |
description: "Whether or not the authentication was valid." | |
}), | |
disableMonitor: true, | |
// this doesnt seem to be important, | |
// login should always be valid when checking on client-side | |
hideFromPalette: true, | |
blockType: BlockType.BOOLEAN | |
}, | |
{ | |
opcode: 'scratchUsername', | |
text: formatMessage({ | |
id: 'jgScratchAuthenticate.blocks.scratchUsername', | |
default: 'scratch username', | |
description: "The username that was logged in." | |
}), | |
disableMonitor: true, | |
blockType: BlockType.REPORTER | |
}, | |
], | |
menus: { | |
loginLocation: { | |
items: '_getLoginLocations', | |
isTypeable: true, | |
}, | |
promptStatus: [ | |
{ text: 'in progress', value: 'inProgress' }, | |
{ text: 'blocked', value: 'blocked' }, | |
{ text: 'complete', value: 'completed' }, | |
{ text: 'closed by the user', value: 'userClosed' }, | |
] | |
} | |
}; | |
} | |
// menus | |
_getLoginLocations() { | |
const nameSplit = document.title.split(" - "); | |
nameSplit.pop(); | |
const projectName = Cast.toString(nameSplit.join(" - ")); | |
return [ | |
projectName === 'PenguinMod' ? 'Project' : projectName, | |
'PenguinMod', | |
'Game', | |
]; | |
} | |
// util | |
async parseLoginCode_() { | |
if (!currentPrivateCode) throw new Error('Private code not present'); | |
const req = await fetch(`https://pm-bapi.vercel.app/api/verifyToken?privateCode=${currentPrivateCode}`); | |
const json = await req.json(); | |
this.loginInfo = { | |
valid: json.valid, | |
username: json.username | |
}; | |
return this.loginInfo; | |
} | |
// blocks | |
showPrompt(args) { | |
// reset | |
this.promptStatus = { | |
inProgress: true, | |
blocked: false, | |
completed: false, | |
userClosed: false, | |
}; | |
this.loginInfo = {}; | |
const loginLocation = Cast.toString(args.NAME); | |
const sanitizedName = encodeURIComponent(loginLocation.substring(0, 256).replace(/[^a-zA-Z0-9 _\-\.\[\]\(\)]+/gmi, "")); | |
const waitingLink = `https://studio.penguinmod.com/scratchAuthExt.html?openLocation=${encodeURIComponent(window.origin)}`; | |
// listen for events before opening | |
let login; | |
let finished = false; | |
const listener = (event) => { | |
if (event.origin !== (new URL(waitingLink)).origin) { | |
return; | |
} | |
if (!(event.data && event.data.scratchauthd1)) { | |
return; | |
} | |
const data = event.data.scratchauthd1; | |
const privateCode = data.pv; | |
currentPrivateCode = privateCode; | |
// update status | |
this.promptStatus.inProgress = false; | |
this.promptStatus.completed = true; | |
finished = true; | |
window.removeEventListener("message", listener); | |
login.close(); | |
}; | |
window.addEventListener("message", listener); | |
// open prompt | |
login = window.open( | |
`https://auth.itinerary.eu.org/auth/?redirect=${btoa(waitingLink)}${sanitizedName.length > 0 ? `&name=${sanitizedName}` : ""}`, | |
"Scratch Authentication", | |
`scrollbars=yes,resizable=yes,status=no,location=yes,toolbar=no,menubar=no,width=768,height=512,left=200,top=200` | |
); | |
if (!login) { | |
// popup was blocked most likely | |
this.promptStatus.inProgress = false; | |
this.promptStatus.blocked = true; | |
return; | |
} | |
// .onclose doesnt work on most platforms it seems | |
// so just set interval | |
const closedInterval = setInterval(() => { | |
if (!login.closed) return; | |
this.promptStatus.inProgress = false; | |
if (!finished) { | |
this.promptStatus.userClosed = true; | |
} | |
window.removeEventListener("message", listener); | |
clearInterval(closedInterval); | |
}, 500); | |
} | |
privateCode() { | |
const code = currentPrivateCode; | |
currentPrivateCode = ''; | |
return code; | |
} | |
serverRedirectLocation() { | |
const waitingLink = `https://studio.penguinmod.com/scratchAuthExt.html?openLocation=${window.origin}`; | |
return waitingLink; | |
} | |
getPromptStatus(args) { | |
const option = Cast.toString(args.STATUS); | |
if (!(option in this.promptStatus)) return false; | |
return this.promptStatus[option]; | |
} | |
// parsing privat4e code blocks | |
async validLogin() { | |
if (Object.keys(this.loginInfo).length <= 0) { | |
try { | |
await this.parseLoginCode_(); | |
} catch { | |
// just say invalid if we cant parse | |
return false; | |
} | |
} | |
return !!this.loginInfo.valid; | |
} | |
async scratchUsername() { | |
if (Object.keys(this.loginInfo).length <= 0) { | |
try { | |
await this.parseLoginCode_(); | |
} catch { | |
// just say no username if we cant parse | |
return ''; | |
} | |
} | |
return Cast.toString(this.loginInfo.username); | |
} | |
// legacy block | |
authenticate(...args) { | |
return Legacy.authenticate(this, ...args); | |
} | |
} | |
module.exports = JgScratchAuthenticateBlocks; | |