UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

1,113 lines • 1.19 MB
var pxt; (function (pxt) { })(pxt || (pxt = {})); (function (pxt) { var analytics; (function (analytics) { const defaultProps = {}; const defaultMeasures = {}; let enabled = false; let ConsoleTickOptions; (function (ConsoleTickOptions) { ConsoleTickOptions[ConsoleTickOptions["Off"] = 0] = "Off"; ConsoleTickOptions[ConsoleTickOptions["Short"] = 1] = "Short"; ConsoleTickOptions[ConsoleTickOptions["Verbose"] = 2] = "Verbose"; })(ConsoleTickOptions = analytics.ConsoleTickOptions || (analytics.ConsoleTickOptions = {})); ; analytics.consoleTicks = ConsoleTickOptions.Off; function addDefaultProperties(props) { Object.keys(props).forEach(k => { if (typeof props[k] == "string") { defaultProps[k] = props[k]; } else { defaultMeasures[k] = props[k]; } }); } analytics.addDefaultProperties = addDefaultProperties; function enable(lang) { if (!pxt.aiTrackException || !pxt.aiTrackEvent || enabled) return; enabled = true; if (typeof lang != "string" || lang.length == 0) { lang = "en"; //Always have a default language. } addDefaultProperties({ lang: lang }); pxt.debug('setting up app insights'); const te = pxt.tickEvent; pxt.tickEvent = function (id, data, opts) { data = pxt.Util.cleanData(data); if (analytics.consoleTicks != ConsoleTickOptions.Off) { const prefix = analytics.consoleTicks == ConsoleTickOptions.Short ? "" : `${new Date().toLocaleTimeString(undefined, { hour12: false })} - Tick - `; const tickInfo = `${id} ${data ? JSON.stringify(data) : "<no data>"} ${opts ? JSON.stringify(opts) : "<no opts>"}`; pxt.log(prefix + tickInfo); } if (te) te(id, data, opts); if (opts === null || opts === void 0 ? void 0 : opts.interactiveConsent) pxt.setInteractiveConsent(true); if (!data) pxt.aiTrackEvent(id); else { const props = Object.assign({}, defaultProps); const measures = Object.assign({}, defaultMeasures); Object.keys(data).forEach(k => { if (typeof data[k] == "string") props[k] = data[k]; else if (typeof data[k] == "number") measures[k] = data[k]; else props[k] = JSON.stringify(data[k] || ''); }); pxt.aiTrackEvent(id, props, measures); } }; const rexp = pxt.reportException; pxt.reportException = function (err, data) { data = pxt.Util.cleanData(data); if (rexp) rexp(err, data); const props = { target: pxt.appTarget.id, version: pxt.appTarget.versions.target }; if (data) pxt.Util.jsonMergeFrom(props, data); pxt.aiTrackException(err, 'exception', props); }; const re = pxt.reportError; pxt.reportError = function (cat, msg, data) { data = pxt.Util.cleanData(data); if (re) re(cat, msg, data); try { throw msg; } catch (err) { const props = { target: pxt.appTarget.id, version: pxt.appTarget.versions.target, category: cat, message: msg }; if (data) pxt.Util.jsonMergeFrom(props, data); pxt.aiTrackException(err, 'error', props); } }; } analytics.enable = enable; function trackPerformanceReport() { if (pxt.perf.perfReportLogged) return; const data = pxt.perf.report(); if (data) { const { durations, milestones } = data; pxt.tickEvent("performance.milestones", milestones); pxt.tickEvent("performance.durations", durations); } } analytics.trackPerformanceReport = trackPerformanceReport; })(analytics = pxt.analytics || (pxt.analytics = {})); })(pxt || (pxt = {})); var pxt; (function (pxt) { })(pxt || (pxt = {})); var pxt; (function (pxt) { var AudioContextManager; (function (AudioContextManager) { let _frequency = 0; let _context; // AudioContext let _vco; // OscillatorNode; let _gain; let _mute = false; //mute audio function context() { if (!_context) _context = freshContext(); return _context; } function freshContext() { window.AudioContext = window.AudioContext || window.webkitAudioContext; if (window.AudioContext) { try { // this call my crash. // SyntaxError: audio resources unavailable for AudioContext construction return new window.AudioContext(); } catch (e) { } } return undefined; } function mute(mute) { if (!_context) return; _mute = mute; stop(); if (mute && _vco) { _vco.disconnect(); _gain.disconnect(); _vco = undefined; _gain = undefined; } } AudioContextManager.mute = mute; function stop() { if (!_context) return; _gain.gain.setTargetAtTime(0, _context.currentTime, 0.015); _frequency = 0; } AudioContextManager.stop = stop; function frequency() { return _frequency; } AudioContextManager.frequency = frequency; function tone(frequency) { if (_mute) return; if (isNaN(frequency) || frequency < 0) return; _frequency = frequency; let ctx = context(); if (!ctx) return; try { if (!_vco) { _vco = ctx.createOscillator(); _vco.type = 'triangle'; _gain = ctx.createGain(); _gain.gain.value = 0; _gain.connect(ctx.destination); _vco.connect(_gain); _vco.start(0); } _vco.frequency.linearRampToValueAtTime(frequency, _context.currentTime); _gain.gain.setTargetAtTime(.2, _context.currentTime, 0.015); } catch (e) { _vco = undefined; return; } } AudioContextManager.tone = tone; })(AudioContextManager = pxt.AudioContextManager || (pxt.AudioContextManager = {})); })(pxt || (pxt = {})); var pxt; (function (pxt) { var auth; (function (auth) { const EMPTY_USERNAME = "??"; const AUTH_CONTAINER = "auth"; // local storage "namespace". const CSRF_TOKEN_KEY = "csrf-token"; // stored in local storage. const AUTH_LOGIN_STATE_KEY = "login-state"; // stored in local storage. const AUTH_USER_STATE_KEY = "user-state"; // stored in local storage. const X_PXT_TARGET = "x-pxt-target"; // header passed in auth rest calls. const INTERACTIVE_LOGIN_UNTIL = "interactive-login-until"; // hint whether to prompt user or try SSO first. let authDisabled = false; auth.DEFAULT_USER_PREFERENCES = () => ({ language: pxt.appTarget.appTheme.defaultLocale, highContrast: false, accessibleBlocks: false, colorThemeIds: {}, reader: "", skillmap: { mapProgress: {}, completedTags: {} }, email: false }); let _client; function client() { if (authDisabled) return undefined; return _client; } auth.client = client; const PREFERENCES_DEBOUNCE_MS = 1 * 1000; const PREFERENCES_DEBOUNCE_MAX_MS = 10 * 1000; let debouncePreferencesChangedTimeout = 0; let debouncePreferencesChangedStarted = 0; // // Local storage of auth token. // // Last known auth token state. This is provided as a convenience for legacy methods that cannot be made async. // Preference hasAuthTokenAsync() over taking a dependency on this cached value. auth.cachedHasAuthToken = false; async function setLocalStorageValueAsync(key, value) { if (!!value) return await pxt.storage.shared.setAsync(AUTH_CONTAINER, key, value); else return await pxt.storage.shared.delAsync(AUTH_CONTAINER, key); } async function getLocalStorageValueAsync(key) { try { return await pxt.storage.shared.getAsync(AUTH_CONTAINER, key); } catch (_a) { return undefined; } } async function getAuthTokenAsync() { const token = await getLocalStorageValueAsync(CSRF_TOKEN_KEY); auth.cachedHasAuthToken = !!token; return token; } auth.getAuthTokenAsync = getAuthTokenAsync; async function setAuthTokenAsync(token) { auth.cachedHasAuthToken = !!token; return await setLocalStorageValueAsync(CSRF_TOKEN_KEY, token); } async function hasAuthTokenAsync() { return !!(await getAuthTokenAsync()); } auth.hasAuthTokenAsync = hasAuthTokenAsync; async function delAuthTokenAsync() { auth.cachedHasAuthToken = false; return await setLocalStorageValueAsync(CSRF_TOKEN_KEY, undefined); } async function getUserStateAsync() { let userState; try { userState = await pxt.storage.shared.getAsync(AUTH_CONTAINER, AUTH_USER_STATE_KEY); } catch (_a) { userState = {}; } auth.cachedUserState = userState; return userState; } auth.getUserStateAsync = getUserStateAsync; async function setUserStateAsync(state) { auth.cachedUserState = Object.assign({}, state); return await pxt.storage.shared.setAsync(AUTH_CONTAINER, AUTH_USER_STATE_KEY, state); } async function delUserStateAsync() { auth.cachedUserState = undefined; return await pxt.storage.shared.delAsync(AUTH_CONTAINER, AUTH_USER_STATE_KEY); } async function getAuthHeadersAsync(authToken) { var _a; const headers = {}; authToken = authToken || (await getAuthTokenAsync()); if (authToken) { headers["authorization"] = `mkcd ${authToken}`; } headers[X_PXT_TARGET] = (_a = pxt.appTarget) === null || _a === void 0 ? void 0 : _a.id; return headers; } auth.getAuthHeadersAsync = getAuthHeadersAsync; class AuthClient { constructor() { this.initialUserPreferences_ = undefined; this.initialAuthCheck_ = undefined; this.patchQueue = []; // Set global instance. _client = this; } async initAsync() { // Initialize user state from local storage. const state = await getUserStateAsync(); this.setUserProfileAsync(state === null || state === void 0 ? void 0 : state.profile); this.setUserPreferencesAsync(state === null || state === void 0 ? void 0 : state.preferences); } /** * Starts the process of authenticating the user against the given identity * provider. Upon success the backend will write an http-only session cookie * to the response, containing the authorization token. This cookie is not * accessible in code, but will be included in all subsequent http requests. * @param idp The id of the identity provider. * @param persistent Whether or not to remember this login across sessions. * @param callbackState The URL hash and params to return to after the auth * flow completes. */ async loginAsync(idp, persistent, callbackState = undefined) { if (!hasIdentity() || !idpEnabled(idp)) { return; } callbackState = callbackState !== null && callbackState !== void 0 ? callbackState : NilCallbackState; // Clear local auth state so we can no longer make authenticated requests. this.clearAuthStateAsync(); // Store some continuation state in local storage so we can return to what // the user was doing before signing in. const genId = () => (Math.PI * Math.random()).toString(36).slice(2); const loginState = { key: genId(), callbackState, callbackPathname: window.location.pathname, idp, persistent }; // Should the user be prompted to interactively login, or can we try to silently login? const interactiveUntil = parseInt(await getLocalStorageValueAsync(INTERACTIVE_LOGIN_UNTIL)); const interactiveLogin = (interactiveUntil || 0) > Date.now(); // Redirect to the login endpoint. const loginUrl = pxt.Util.stringifyQueryString('/api/auth/login', { response_type: "token", provider: idp, persistent, redirect_uri: `${window.location.origin}${window.location.pathname}?authcallback=1&state=${loginState.key}`, prompt: interactiveLogin ? "select_account" : "silent" }); const apiResult = await this.apiAsync(loginUrl); if (apiResult.success) { loginState.authCodeVerifier = apiResult.resp.authCodeVerifier; // will be undefined unless configured for the target await pxt.storage.shared.setAsync(AUTH_CONTAINER, AUTH_LOGIN_STATE_KEY, loginState); pxt.tickEvent('auth.login.start', { 'provider': idp }); window.location.href = apiResult.resp.loginUrl; } else { try { await this.onSignInFailed(); } catch (_a) { } } } /** * Sign out the user and clear the auth token cookie. */ async logoutAsync(continuationHash) { if (!hasIdentity()) { return; } await AuthClient.staticLogoutAsync(continuationHash); try { await this.onStateCleared(); } catch (_a) { } try { await this.onSignedOut(); } catch (_b) { } } /** * Sign out the user and clear the auth token cookie. */ static async staticLogoutAsync(continuationHash) { if (!hasIdentity()) { return; } pxt.tickEvent('auth.logout'); // Indicate that for the next minute, signin should be interactive. // Use case: SSO signed in with the wrong account. User wants to sign in with a different account. await setLocalStorageValueAsync(INTERACTIVE_LOGIN_UNTIL, (Date.now() + 60000).toString()); continuationHash = continuationHash ? continuationHash.startsWith('#') ? continuationHash : `#${continuationHash}` : ""; const clientRedirect = `${window.location.origin}${window.location.pathname}${window.location.search}${continuationHash}`; // Tell backend to clear the http-only auth cookie. let logoutUri = ""; try { const uri = pxt.Util.stringifyQueryString('/api/auth/logout', { redirect_uri: clientRedirect, authcallback: '1' }); const apiResult = await AuthClient.staticApiAsync(uri); if (apiResult.success) { logoutUri = apiResult.resp.logoutUrl; } } catch (_a) { // Ignore errors. } // Clear local auth state so we can no longer make authenticated requests. await delAuthTokenAsync(); await delUserStateAsync(); if (pxt.BrowserUtils.hasWindow()) { if (logoutUri) { // Redirect to logout endpoint window.location.href = logoutUri; } else { // Redirect to home screen window.location.href = clientRedirect; location.reload(); } } } async deleteProfileAsync() { var _a; // only if we're logged in if (!await this.loggedInAsync()) { return; } const state = await getUserStateAsync(); const userId = (_a = state === null || state === void 0 ? void 0 : state.profile) === null || _a === void 0 ? void 0 : _a.id; const res = await this.apiAsync('/api/user', null, 'DELETE'); if (res.err) { try { await this.onApiError((res.err)); } catch (_b) { } } else { try { // Clear local auth state so we can no longer make authenticated requests. await this.clearAuthStateAsync(); try { await this.onProfileDeleted(userId); } catch (_c) { } } finally { pxt.tickEvent('auth.profile.deleted'); } } } async initialUserPreferencesAsync() { // only if we're logged in if (!await this.loggedInAsync()) { return undefined; } if (!this.initialUserPreferences_) { this.initialUserPreferences_ = this.fetchUserPreferencesAsync(); } return this.initialUserPreferences_; } async userProfileAsync() { if (!await this.loggedInAsync()) { return undefined; } const state = await getUserStateAsync(); return Object.assign({}, state.profile); } async userPreferencesAsync() { //if (!await this.loggedInAsync()) { return undefined; } // allow even when not signed in. const state = await getUserStateAsync(); return Object.assign({}, state.preferences); } /** * Checks to see if we're already logged in by trying to fetch user info from * the backend. If we have a valid auth token cookie, it will succeed. */ async authCheckAsync() { var _a; if (!hasIdentity()) { return undefined; } if (!(await hasAuthTokenAsync())) { return undefined; } const state = await getUserStateAsync(); if ((_a = state === null || state === void 0 ? void 0 : state.profile) === null || _a === void 0 ? void 0 : _a.id) { // If we already have a user profile, return it. if (!this.initialAuthCheck_) { this.initialAuthCheck_ = Promise.resolve(state.profile); } } else if (!this.initialAuthCheck_) { // Optimistically try to fetch user profile. It will succeed if we have a valid // session cookie. Upon success, virtual api state will be updated, and the UI // will update accordingly. this.initialAuthCheck_ = this.fetchUserAsync(); } return this.initialAuthCheck_; } async loggedInAsync() { const profile = await this.authCheckAsync(); if (!profile) return false; return await this.hasUserIdAsync(); } async updateUserProfileAsync(opts) { if (!await this.loggedInAsync()) { return false; } const state = await getUserStateAsync(); const result = await this.apiAsync('/api/user/profile', { id: state.profile.id, username: opts.username, avatarUrl: opts.avatarUrl }); if (result.success) { // Set user profile from returned value await this.setUserProfileAsync(result.resp); } return result.success; } async patchUserPreferencesAsync(patchOps, opts = {}) { const defaultSuccessAsync = async () => ({ success: true, res: await this.userPreferencesAsync() }); patchOps = Array.isArray(patchOps) ? patchOps : [patchOps]; patchOps = patchOps.filter(op => !!op); if (!patchOps.length) { return await defaultSuccessAsync(); } const patchDiff = (pSrc, ops, filter) => { // Apply patches to pDst and return the diff as a set of new patch ops. const pDst = pxt.U.deepCopy(pSrc); ts.pxtc.jsonPatch.patchInPlace(pDst, ops); let diff = ts.pxtc.jsonPatch.diff(pSrc, pDst); // Run caller-provided filter if (diff.length && filter) { diff = diff.filter(filter); } return diff; }; // Process incoming patch operations to produce a more fine-grained set of diffs. Incoming patches may be overly destructive // Apply the patch in isolation and get the diff from original const curPref = await this.userPreferencesAsync(); const diff = patchDiff(curPref, patchOps, opts.filter); if (!diff.length) { return await defaultSuccessAsync(); } // Apply the new diff to the current state ts.pxtc.jsonPatch.patchInPlace(curPref, diff); await this.setUserPreferencesAsync(curPref); // If the user is not logged in, non-persistent local state is all we'll use (no sync to cloud) if (!await this.loggedInAsync()) { return await defaultSuccessAsync(); } // If the user is logged in, sync to cloud, but debounce the api call as this can be called frequently from skillmaps // Queue the patch for sync with backend this.patchQueue.push({ ops: patchOps, filter: opts.filter }); clearTimeout(debouncePreferencesChangedTimeout); const syncPrefs = async () => { debouncePreferencesChangedStarted = 0; if (!this.patchQueue.length) { return await defaultSuccessAsync(); } // Fetch latest prefs from remote const getResult = await this.apiAsync('/api/user/preferences'); if (!getResult.success) { pxt.reportError("identity", "failed to fetch preferences for patch", getResult); return { success: false, res: undefined }; } // Apply queued patches to the remote state in isolation and develop a final diff to send to the backend const remotePrefs = pxt.U.deepCopy(getResult.resp) || auth.DEFAULT_USER_PREFERENCES(); const patchQueue = this.patchQueue; this.patchQueue = []; // Reset the queue patchQueue.forEach(patch => { const diff = patchDiff(remotePrefs, patch.ops, patch.filter); ts.pxtc.jsonPatch.patchInPlace(remotePrefs, diff); }); // Diff the original and patched remote states to get a final set of patch operations const finalOps = pxtc.jsonPatch.diff(getResult.resp, remotePrefs); const patchResult = await this.apiAsync('/api/user/preferences', finalOps, 'PATCH'); if (patchResult.success) { // Set user profile from returned value so we stay in sync this.setUserPreferencesAsync(patchResult.resp); } else { pxt.reportError("identity", "failed to patch preferences", patchResult); } return { success: patchResult.success, res: patchResult.resp }; }; if (opts.immediate) { return await syncPrefs(); } else { if (!debouncePreferencesChangedStarted) { debouncePreferencesChangedStarted = pxt.U.now(); } if (PREFERENCES_DEBOUNCE_MAX_MS < pxt.U.now() - debouncePreferencesChangedStarted) { return await syncPrefs(); } else { debouncePreferencesChangedTimeout = setTimeout(syncPrefs, PREFERENCES_DEBOUNCE_MS); return { success: false, res: undefined }; // This needs to be implemented correctly to return a promise with the debouncer } } } async hasUserIdAsync() { var _a; if (!hasIdentity()) { return false; } if (!(await hasAuthTokenAsync())) { return undefined; } const state = await getUserStateAsync(); return !!((_a = state === null || state === void 0 ? void 0 : state.profile) === null || _a === void 0 ? void 0 : _a.id); } async fetchUserAsync() { var _a; if (!hasIdentity()) { return undefined; } if (!(await hasAuthTokenAsync())) { return undefined; } const state = await getUserStateAsync(); // We already have a user, no need to get it again. if ((_a = state === null || state === void 0 ? void 0 : state.profile) === null || _a === void 0 ? void 0 : _a.id) { return state.profile; } const result = await this.apiAsync('/api/user/profile'); if (result.success) { const profile = result.resp; await this.setUserProfileAsync(profile); return profile; } return undefined; } async setUserProfileAsync(profile) { const wasLoggedIn = await this.hasUserIdAsync(); await this.transformUserProfileAsync(profile); const isLoggedIn = await this.hasUserIdAsync(); try { await this.onUserProfileChanged(); } catch (_a) { } //pxt.data.invalidate(USER_PROFILE); if (isLoggedIn && !wasLoggedIn) { try { await this.onSignedIn(); } catch (_b) { } //pxt.data.invalidate(LOGGED_IN); } else if (!isLoggedIn && wasLoggedIn) { try { await this.onSignedOut(); } catch (_c) { } //pxt.data.invalidate(LOGGED_IN); } } async setUserPreferencesAsync(newPref) { var _a; const state = await getUserStateAsync(); const oldPref = (_a = state === null || state === void 0 ? void 0 : state.preferences) !== null && _a !== void 0 ? _a : auth.DEFAULT_USER_PREFERENCES(); const diff = ts.pxtc.jsonPatch.diff(oldPref, newPref); // update const finalPref = await this.transformUserPreferencesAsync(Object.assign(Object.assign({}, oldPref), newPref)); try { await this.onUserPreferencesChanged(diff); } catch (_b) { } return finalPref; } async fetchUserPreferencesAsync() { // Wait for the initial auth if (!await this.loggedInAsync()) { return undefined; } const state = await getUserStateAsync(); const result = await this.apiAsync('/api/user/preferences'); if (result.success) { // Set user profile from returned value if (result.resp) { // Note the cloud should send partial information back if it is missing // a field. So e.g. if the language has never been set in the cloud, it won't // overwrite the local state. const prefs = this.setUserPreferencesAsync(result.resp); // update our one-time promise for the initial load return prefs; } } return undefined; } /** * Updates user profile state and writes it to local storage. */ async transformUserProfileAsync(profile) { let state = await getUserStateAsync(); state = Object.assign(Object.assign({}, state), { profile: Object.assign({}, profile) }); await setUserStateAsync(state); return state.profile; } /** * Updates user preference state and writes it to local storage. */ async transformUserPreferencesAsync(preferences) { let state = await getUserStateAsync(); state = Object.assign(Object.assign({}, state), { preferences: Object.assign({}, preferences) }); await setUserStateAsync(state); return state.preferences; } /** * Clear local auth state then call the onStateCleared callback. */ async clearAuthStateAsync() { await delAuthTokenAsync(); await delUserStateAsync(); try { await this.onStateCleared(); } catch (_a) { } } /*protected*/ async apiAsync(url, data, method, authToken) { return await AuthClient.staticApiAsync(url, data, method, authToken); } static async staticApiAsync(url, data, method, authToken) { const headers = await getAuthHeadersAsync(authToken); url = pxt.BrowserUtils.isLocalHostDev() ? `${pxt.cloud.DEV_BACKEND}${url}` : url; return pxt.Util.requestAsync({ url, headers, data, method: method ? method : data ? "POST" : "GET", withCredentials: true, // Include cookies and authorization header in request, subject to CORS policy. }).then(r => { return { statusCode: r.statusCode, resp: r.json, success: Math.floor(r.statusCode / 100) === 2, err: null }; }).catch(async (e) => { if (!/logout/.test(url) && e.statusCode == 401) { // 401/Unauthorized. logout now. await AuthClient.staticLogoutAsync(); } return { statusCode: e.statusCode, err: e, resp: null, success: false }; }); } } auth.AuthClient = AuthClient; const NilCallbackState = { hash: '', params: {} }; async function loginCallbackAsync(qs) { let loginState; let callbackState = Object.assign({}, NilCallbackState); do { // Read and remove auth state from local storage loginState = await pxt.storage.shared.getAsync(AUTH_CONTAINER, AUTH_LOGIN_STATE_KEY); if (!loginState) { pxt.debug("Auth state not found in storge."); return; } await pxt.storage.shared.delAsync(AUTH_CONTAINER, AUTH_LOGIN_STATE_KEY); const stateKey = qs['state']; if (!stateKey || loginState.key !== stateKey) { pxt.debug("Failed to get auth state for key"); return; } callbackState = Object.assign(Object.assign({}, NilCallbackState), loginState.callbackState); const error = qs['error']; if (error) { // Possible values for 'error': // 'invalid_request' -- Something is wrong with the request itself. // 'access_denied' -- The identity provider denied the request, or user canceled it. const error_description = qs['error_description']; pxt.tickEvent('auth.login.error', { 'error': error, 'provider': loginState.idp }); pxt.log(`Auth failed: ${error}:${error_description}`); // TODO: Is it correct to clear continuation hash? callbackState = Object.assign({}, NilCallbackState); // TODO: Show a message to the user (via rewritten continuation path)? break; } const authToken = qs['token']; if (!authToken) { pxt.debug("Missing authToken in auth callback."); break; } // If this auth request was assigned an auth code, claim it now. This will set // the required auth cookie in this domain (for cross-domain authentication). if (loginState.authCodeVerifier) { const otacCheckUrl = pxt.Util.stringifyQueryString('/api/otac/check', { persistent: loginState.persistent, }); await AuthClient.staticApiAsync(otacCheckUrl, null, null, loginState.authCodeVerifier); } // Store csrf token in local storage. It is ok to do this even when // "Remember me" wasn't selected because this token is not usable // without its cookie-based counterpart. When "Remember me" is false, // the cookie is not persisted. await setAuthTokenAsync(authToken); // Clear interactive login flag. Next auth request will try silent SSO first. await setLocalStorageValueAsync(INTERACTIVE_LOGIN_UNTIL, undefined); pxt.tickEvent('auth.login.success', { 'provider': loginState.idp }); } while (false); // Clear url parameters and redirect to the callback location. const hash = callbackState.hash.startsWith('#') ? callbackState.hash : `#${callbackState.hash}`; const params = pxt.Util.stringifyQueryString('', callbackState.params); const pathname = loginState.callbackPathname.startsWith('/') ? loginState.callbackPathname : `/${loginState.callbackPathname}`; const redirect = `${pathname}${params}${hash}`; window.location.href = redirect; } auth.loginCallbackAsync = loginCallbackAsync; function identityProviders() { var _a, _b; return Object.keys(((_b = (_a = pxt.appTarget) === null || _a === void 0 ? void 0 : _a.cloud) === null || _b === void 0 ? void 0 : _b.cloudProviders) || {}) .map(id => pxt.appTarget.cloud.cloudProviders[id]) .filter(prov => prov.identity) .sort((a, b) => a.order - b.order); } auth.identityProviders = identityProviders; function identityProvider(id) { return identityProviders().filter(prov => prov.id === id).shift(); } auth.identityProvider = identityProvider; function hasIdentity() { return !authDisabled && !pxt.BrowserUtils.isPxtElectron() && identityProviders().length > 0; } auth.hasIdentity = hasIdentity; function idpEnabled(idp) { return identityProviders().filter(prov => prov.id === idp).length > 0; } function enableAuth(enabled = true) { authDisabled = !enabled; } auth.enableAuth = enableAuth; function userName(user) { var _a, _b, _c, _d; return (_d = (_b = (_a = user === null || user === void 0 ? void 0 : user.idp) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : (_c = user === null || user === void 0 ? void 0 : user.idp) === null || _c === void 0 ? void 0 : _c.username) !== null && _d !== void 0 ? _d : EMPTY_USERNAME; } auth.userName = userName; function identityProviderId(user) { var _a; return (_a = user === null || user === void 0 ? void 0 : user.idp) === null || _a === void 0 ? void 0 : _a.provider; } auth.identityProviderId = identityProviderId; function firstName(user) { const userName = pxt.auth.userName(user); return (userName === null || userName === void 0 ? void 0 : userName.split(" ").shift()) || userName; } auth.firstName = firstName; function userInitials(user) { const username = pxt.auth.userName(user); return ts.pxtc.Util.initials(username); } auth.userInitials = userInitials; function generateUserProfilePicDataUrl(profile) { var _a, _b; if ((_b = (_a = profile === null || profile === void 0 ? void 0 : profile.idp) === null || _a === void 0 ? void 0 : _a.picture) === null || _b === void 0 ? void 0 : _b.encoded) { const url = window.URL || window.webkitURL; try { // Decode the base64 image to a data URL. const decoded = pxt.Util.stringToUint8Array(atob(profile.idp.picture.encoded)); const blob = new Blob([decoded], { type: profile.idp.picture.mimeType }); profile.idp.picture.dataUrl = url.createObjectURL(blob); } catch (_c) { } } } auth.generateUserProfilePicDataUrl = generateUserProfilePicDataUrl; /** * Checks only the ID and sourceURL */ function badgeEquals(badgeA, badgeB) { return badgeA.id === badgeB.id && badgeA.sourceURL === badgeB.sourceURL; } auth.badgeEquals = badgeEquals; function hasBadge(preferences, badge) { return preferences.badges.some(toCheck => badgeEquals(toCheck, badge)); } auth.hasBadge = hasBadge; })(auth = pxt.auth || (pxt.auth = {})); })(pxt || (pxt = {})); // Needs to be in its own file to avoid a circular dependency: util.ts -> main.ts -> util.ts var pxt; (function (pxt) { /** * Track an event. */ pxt.tickEvent = function (id) { }; })(pxt || (pxt = {})); var pxt; (function (pxt) { let LogLevel; (function (LogLevel) { LogLevel[LogLevel["Debug"] = 0] = "Debug"; LogLevel[LogLevel["Info"] = 1] = "Info"; LogLevel[LogLevel["Log"] = 1] = "Log"; LogLevel[LogLevel["Warning"] = 2] = "Warning"; LogLevel[LogLevel["Error"] = 3] = "Error"; })(LogLevel = pxt.LogLevel || (pxt.LogLevel = {})); class ConsoleLogger { constructor() { this.setLogLevel(LogLevel.Info); } setLogLevel(level) { this.logLevel = level; } getLogLevel() { return this.logLevel; } info(...args) { if (!this.shouldLog(LogLevel.Info)) return; if (console === null || console === void 0 ? void 0 : console.info) { console.info.call(null, ...args); } } log(...args) { if (!this.shouldLog(LogLevel.Log)) return; if (console === null || console === void 0 ? void 0 : console.log) { console.log.call(null, ...args); } } debug(...args) { if (!this.shouldLog(LogLevel.Debug)) return; if (console === null || console === void 0 ? void 0 : console.debug) { console.debug.call(null, ...args); } } error(...args) { if (!this.shouldLog(LogLevel.Error)) return; if (console === null || console === void 0 ? void 0 : console.error) { console.error.call(null, ...args); } } warn(...args) { if (!this.shouldLog(LogLevel.Warning)) return; if (console === null || console === void 0 ? void 0 : console.warn) { console.warn.call(null, ...args); } } shouldLog(level) { return level >= this.logLevel; } } pxt.ConsoleLogger = ConsoleLogger; let logger = new ConsoleLogger(); function info(...args) { logger.info(...args); } pxt.info = info; function log(...args) { logger.log(...args); } pxt.log = log; function debug(...args) { logger.debug(...args); } pxt.debug = debug; function error(...args) { logger.error(...args); } pxt.error = error; function warn(...args) { logger.warn(...args); } pxt.warn = warn; function setLogger(impl) { const level = logger === null || logger === void 0 ? void 0 : logger.getLogLevel(); logger = impl; if (level !== undefined) { logger.setLogLevel(level); } } pxt.setLogger = setLogger; function setLogLevel(level) { logger.setLogLevel(level); } pxt.setLogLevel = setLogLevel; })(pxt || (pxt = {})); /// <reference path="./tickEvent.ts" /> /// <reference path="./apptarget.ts" /> /// <reference path="./logger.ts" /> var ts; (function (ts) { var pxtc; (function (pxtc) { pxtc.__dummy = 42; })(pxtc = ts.pxtc || (ts.pxtc = {})); })(ts || (ts = {})); var pxtc = ts.pxtc; (function (ts) { var pxtc; (function (pxtc) { var Util; (function (Util) { function assert(cond, msg = "Assertion failed") { if (!cond) { debugger; throw new Error(msg); } } Util.assert = assert; function flatClone(obj) { if (obj == null) return null; let r = {}; Object.keys(obj).forEach((k) => { r[k] = obj[k]; }); return r; } Util.flatClone = flatClone; function clone(v) { if (v == null) return null; return JSON.parse(JSON.stringify(v)); } Util.clone = clone; function htmlEscape(_input) { if (!_input) return _input; // null, undefined, empty string test return _input.replace(/([^\w .!?\-$])/g, c => "&#" + c.charCodeAt(0) + ";"); } Util.htmlEscape = htmlEscape; function htmlUnescape(_input) { if (!_input) return _input; // null, undefined, empty string test return _input.replace(/(&#\d+;)/g, c => String.fromCharCode(Number(c.substr(2, c.length - 3)))); } Util.htmlUnescape = htmlUnescape; function jsStringQuote(s) { return s.replace(/[^\w .!?\-$]/g, (c) => { let h = c.charCodeAt(0).toString(16); return "\\u" + "0000".substr(0, 4 - h.length) + h; }); } Util.jsStringQuote = jsStringQuote; function jsStringLiteral(s) { return "\"" + jsStringQuote(s) + "\""; } Util.jsStringLiteral = jsStringLiteral; function initials(username) { if (/^\w+@/.test(username)) { // Looks like an email address. Return first two characters. const initials = username.match(/^\w\w/); return initials.shift().toUpperCase(); } else { // Parse the user name for user initials const initials = username.match(/\b\w/g) || []; return ((initials.shift() || '') + (initials.pop() || '')).toUpperCase(); } } Util.initials = initials; // Localization functions. Please port any modifications over to pxtsim/localization.ts let _localizeLang = "en"; let _localizeStrings = {}; let _translationsCache = {}; //let _didSetlocalizations = false; //let _didReportLocalizationsNotSet = false; let localizeLive = false; function enableLiveLocalizationUpdates() { localizeLive = true; } Util.enableLiveLocalizationUpdates = enableLiveLocalizationUpdates; function liveLocalizationEnabled() { return localizeLive; } Util.liveLocalizationEnabled = liveLocalizationEnabled; /** * Returns the current user language, prepended by "live-" if in live mode */ function localeInfo() { return `${localizeLive ? "live-" : ""}${userLanguage()}`; } Util.localeInfo = localeInfo; /** * Returns current user language iSO-code. Default is `en`. */ function userLanguage() { return _localizeLang; } Util.userLanguage = userLanguage; // This function returns normalized language code // For example: zh-CN this returns ["zh-CN", "zh", "zh-cn"] // First two are valid crowdin\makecode locale code, // Last all lowercase one is just for the backup when reading user defined extensions & tutorials. function normalizeLanguageCode(code) { const langParts = /^(\w{2,3})-(\w{2,4}$)/i.exec(code); if (langParts && langParts[1] && langParts[2]) { return [`${langParts[1].toLowerCase()}-${langParts[2].toUpperCase()}`, langParts[1].toLowerCase(), `${langParts[1].toLowerCase()}-${langParts[2].toLowerCase()}`]; } else { return [(code || "en").toLowerCase()]; } } Util.normalizeLanguageCode = normalizeLanguageCode; function setUserLanguage(localizeLang) { _localizeLang = normalizeLanguageCode(localizeLang)[