pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
1,083 lines (1,082 loc) • 1.13 MB
JavaScript
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) {
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>"}`;
console.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) {
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) {
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;
})(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.
let authDisabled = false;
auth.DEFAULT_USER_PREFERENCES = () => ({
highContrast: false,
language: pxt.appTarget.appTheme.defaultLocale,
reader: "",
skillmap: { mapProgress: {}, completedTags: {} },
email: false
});
let _client;
function client() { return _client; }
auth.client = client;
const PREFERENCES_DEBOUNCE_MS = 1 * 1000;
const PREFERENCES_DEBOUNCE_MAX_MS = 10 * 1000;
let debouncePreferencesChangedTimeout = 0;
let debouncePreferencesChangedStarted = 0;
class AuthClient {
constructor() {
this.initialUserPreferences_ = undefined;
this.initialAuthCheck_ = undefined;
this.patchQueue = [];
// Set global instance.
_client = this;
}
async initAsync() {
// Load state from local storage
try {
this.state$ = await pxt.storage.shared.getAsync(AUTH_CONTAINER, AUTH_USER_STATE_KEY);
}
catch (_a) { }
if (!this.state$) {
this.state$ = {};
}
this.setUserProfileAsync(this.state$.profile);
this.setUserPreferencesAsync(this.state$.preferences);
}
async authTokenAsync() {
return await pxt.storage.shared.getAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY);
}
/**
* 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) {
var _a;
if (!hasIdentity() || !idpEnabled(idp)) {
return;
}
callbackState = callbackState !== null && callbackState !== void 0 ? callbackState : NilCallbackState;
const state = this.getState();
// See if we have a valid access token already.
if (!state.profile) {
await this.authCheckAsync();
}
const currIdp = (_a = state.profile) === null || _a === void 0 ? void 0 : _a.idp;
// Check if we're already signed into this identity provider.
if ((currIdp === null || currIdp === void 0 ? void 0 : currIdp.provider) === idp) {
pxt.debug(`loginAsync: Already signed into ${idp}.`);
return;
}
this.clearState();
// 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
};
// 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}`
});
const apiResult = await this.apiAsync(loginUrl);
if (apiResult.success) {
loginState.authCodeVerifier = apiResult.resp.authCodeVerifier;
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 {
await this.onSignInFailed();
}
}
/**
* Sign out the user and clear the auth token cookie.
*/
async logoutAsync(continuationHash) {
if (!hasIdentity()) {
return;
}
this.clearState();
return await AuthClient.staticLogoutAsync(continuationHash);
}
/**
* Sign out the user and clear the auth token cookie.
*/
static async staticLogoutAsync(continuationHash) {
if (!hasIdentity()) {
return;
}
pxt.tickEvent('auth.logout');
// backend will clear the cookie token and pass back the provider logout endpoint.
await AuthClient.staticApiAsync('/api/auth/logout');
// Clear csrf token so we can no longer make authenticated requests.
await pxt.storage.shared.delAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY);
// Update state and UI to reflect logged out state.
const hash = continuationHash ? continuationHash.startsWith('#') ? continuationHash : `#${continuationHash}` : "";
// Redirect to home screen, or skillmap home screen
if (pxt.BrowserUtils.hasWindow()) {
window.location.href = `${window.location.origin}${window.location.pathname}${hash}`;
location.reload();
}
}
async deleteProfileAsync() {
var _a;
// only if we're logged in
if (!await this.loggedInAsync()) {
return;
}
const userId = (_a = this.getState().profile) === null || _a === void 0 ? void 0 : _a.id;
const res = await this.apiAsync('/api/user', null, 'DELETE');
if (res.err) {
await this.onApiError((res.err));
}
else {
try {
// Clear csrf token so we can no longer make authenticated requests.
await pxt.storage.shared.delAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY);
try {
await this.onProfileDeleted(userId);
}
catch (_b) {
pxt.tickEvent('auth.profile.cloudToLocalFailed');
}
// Update state and UI to reflect logged out state.
this.clearState();
}
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 = this.getState();
return Object.assign({}, state.profile);
}
async userPreferencesAsync() {
//if (!await this.loggedInAsync()) { return undefined; } // allow even when not signed in.
const state = this.getState();
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;
}
// Fail fast if we don't have csrf token.
if (!(await pxt.storage.shared.getAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY))) {
return undefined;
}
const state = this.getState();
if ((_a = state.profile) === null || _a === void 0 ? void 0 : _a.id) {
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() {
await this.authCheckAsync();
return this.hasUserId();
}
async updateUserProfileAsync(opts) {
if (!await this.loggedInAsync()) {
return false;
}
const state = this.getState();
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
}
}
}
/*protected*/ hasUserId() {
var _a;
if (!hasIdentity()) {
return false;
}
const state = this.getState();
return !!((_a = state.profile) === null || _a === void 0 ? void 0 : _a.id);
}
async fetchUserAsync() {
var _a;
if (!hasIdentity()) {
return undefined;
}
const state = this.getState();
// We already have a user, no need to get it again.
if ((_a = 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 = this.hasUserId();
this.transformUserProfile(profile);
const isLoggedIn = this.hasUserId();
this.onUserProfileChanged();
//pxt.data.invalidate(USER_PROFILE);
if (isLoggedIn && !wasLoggedIn) {
await this.onSignedIn();
//pxt.data.invalidate(LOGGED_IN);
}
else if (!isLoggedIn && wasLoggedIn) {
await this.onSignedOut();
//pxt.data.invalidate(LOGGED_IN);
}
}
async setUserPreferencesAsync(newPref) {
var _a;
const oldPref = (_a = this.getState().preferences) !== null && _a !== void 0 ? _a : auth.DEFAULT_USER_PREFERENCES();
const diff = ts.pxtc.jsonPatch.diff(oldPref, newPref);
// update
this.transformUserPreferences(Object.assign(Object.assign({}, oldPref), newPref));
await this.onUserPreferencesChanged(diff);
}
async fetchUserPreferencesAsync() {
// Wait for the initial auth
if (!await this.loggedInAsync()) {
return undefined;
}
const state = this.getState();
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.
this.setUserPreferencesAsync(result.resp);
// update our one-time promise for the initial load
return state.preferences;
}
}
return undefined;
}
/**
* Updates user profile state and writes it to local storage.
* Direct access to state$ allowed.
*/
transformUserProfile(profile) {
this.state$ = Object.assign(Object.assign({}, this.state$), { profile: Object.assign({}, profile) });
this.saveState();
}
/**
* Updates user preference state and writes it to local storage.
* Direct access to state$ allowed.
*/
transformUserPreferences(preferences) {
this.state$ = Object.assign(Object.assign({}, this.state$), { preferences: Object.assign({}, preferences) });
this.saveState();
}
/**
* Read-only access to current state.
* Direct access to state$ allowed.
*/
/*private*/ getState() {
return this.state$;
}
;
/**
* Write auth state to local storage.
* Direct access to state$ allowed.
*/
saveState() {
pxt.storage.shared.setAsync(AUTH_CONTAINER, AUTH_USER_STATE_KEY, this.state$).then(() => { });
}
/**
* Clear all auth state.
* Direct access to state$ allowed.
*/
clearState() {
this.state$ = {};
pxt.storage.shared.delAsync(AUTH_CONTAINER, AUTH_USER_STATE_KEY)
.then(() => this.onStateCleared());
}
/*protected*/ async apiAsync(url, data, method, authToken) {
return await AuthClient.staticApiAsync(url, data, method, authToken);
}
static async staticApiAsync(url, data, method, authToken) {
var _a;
const headers = {};
authToken = authToken || (await pxt.storage.shared.getAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY));
if (authToken) {
headers["authorization"] = `mkcd ${authToken}`;
}
headers[X_PXT_TARGET] = (_a = pxt.appTarget) === null || _a === void 0 ? void 0 : _a.id;
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 pxt.storage.shared.setAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY, authToken);
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}${hash}${params}`;
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() {
var _a, _b, _c, _d;
return (_d = (_c = (_b = (_a = client()) === null || _a === void 0 ? void 0 : _a.getState()) === null || _b === void 0 ? void 0 : _b.profile) === null || _c === void 0 ? void 0 : _c.idp) === null || _d === void 0 ? void 0 : _d.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 = {}));
/// <reference path="./tickEvent.ts" />
/// <reference path="./apptarget.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})-(\w{2}$)/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)[0];
}
Util.setUserLanguage = setUserLanguage;
function isUserLanguageRtl() {
return /^ar|dv|fa|ha|he|ks|ku|ps|ur|yi/i.test(_localizeLang);
}
Util.isUserLanguageRtl = isUserLanguageRtl;
Util.TRANSLATION_LOCALE = "pxt";
function isTranslationMode() {
return userLanguage() == Util.TRANSLATION_LOCALE;
}
Util.isTranslationMode = isTranslationMode;
function _localize(s) {
// Needs to be test in localhost / CLI
/*if (!_didSetlocalizations && !_didReportLocalizationsNotSet) {
_didReportLocalizationsNotSet = true;
pxt.tickEvent("locale.localizationsnotset");
// pxt.reportError can't be used here because of order of file imports
// Just use console.error instead, and use an Error so stacktrace is reported
console.error(new Error("Attempted to translate a string before localizations were set"));
}*/
return _localizeStrings[s] || s;
}
Util._localize = _localize;
function getLocalizedStrings() {
return _localizeStrings;
}
Util.getLocalizedStrings = getLocalizedStrings;
function setLocalizedStrings(strs) {
//_didSetlocalizations = true;
_localizeStrings = strs;
}
Util.setLocalizedStrings = setLocalizedStrings;
function translationsCache() {
return _translationsCache;
}
Util.translationsCache = translationsCache;
function fmt_va(f, args) {
if (args.length == 0)
return f;
return f.replace(/\{([0-9]+)(\:[^\}]+)?\}/g, function (s, n, spec) {
let v = args[parseInt(n)];
let r = "";
let fmtMatch = /^:f(\d*)\.(\d+)/.exec(spec);
if (fmtMatch) {
let precision = parseInt(fmtMatch[2]);
let len = parseInt(fmtMatch[1]) || 0;
let fillChar = /^0/.test(fmtMatch[1]) ? "0" : " ";
let num = v.toFixed(precision);
if (len > 0 && precision > 0)
len += precision + 1;
if (len > 0) {
while (num.length < len) {
num = fillChar + num;
}
}
r = num;
}
else if (spec == ":x") {
r = "0x" + v.toString(16);
}
else if (v === undefined)
r = "(undef)";
else if (v === null)
r = "(null)";
else if (v.toString)
r = v.toString();
else
r = v + "";
if (spec == ":a") {
if (/^\s*[euioah]/.test(r.toLowerCase()))
r = "an " + r;
else if (/^\s*[bcdfgjklmnpqrstvwxz]/.test(r.toLowerCase()))
r = "a " + r;
}
else if (spec == ":s") {
if (v == 1)
r = "";
else
r = "s";
}
else if (spec == ":q") {
r = Util.htmlEscape(r);
}
else if (spec == ":jq") {
r = Util.jsStringQuote(r);
}
else if (spec == ":uri") {
r = encodeURIComponent(r).replace(/'/g, "%27").replace(/"/g, "%22");
}
else if (spec == ":url") {
r = encodeURI(r).replace(/'/g, "%27").replace(/"/g, "%22");
}
else if (spec == ":%") {
r = (v * 100).toFixed(1).toString() + '%';
}
return r;
});
}
Util.fmt_va = fmt_va;
function fmt(f, ...args) { return fmt_va(f, args); }
Util.fmt = fmt;
const locStats = {};
function dumpLocStats() {
const r = {};
Object.keys(locStats).sort((a, b) => locStats[b] - locStats[a])
.forEach(k => r[k] = k);
console.log('prioritized list of strings:');
console.log(JSON.stringify(r, null, 2));
}
Util.dumpLocStats = dumpLocStats;
let sForPlural = true;
function lf_va(format, args) {
if (!format)
return format;
locStats[format] = (locStats[format] || 0) + 1;
let lfmt = Util._localize(format);
if (!sForPlural && lfmt != format && /\d:s\}/.test(lfmt)) {
lfmt = lfmt.replace(/\{\d+:s\}/g, "");
}
lfmt = lfmt.replace(/^\{(id|loc):[^\}]+\}/g, '');
return fmt_va(lfmt, args);
}
Util.lf_va = lf_va;
function lf(format, ...args) {
return lf_va(format, args); // @ignorelf@
}
Util.lf = lf;
/**
* Similar to lf but the string do not get extracted into the loc file.
*/
function rlf(format, ...args) {
return lf_va(format, args); // @ignorelf@
}
Util.rlf = rlf;
function lookup(m, key) {
if (m.hasOwnProperty(key))
return m[key];
return null;
}
Util.lookup = lookup;
function isoTime(time) {
let d = new Date(time * 1000);
return Util.fmt("{0}-{1:f02.0}-{2:f02.0} {3:f02.0}:{4:f02.0}:{5:f02.0}", d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds());
}
Util.isoTime = isoTime;
function userError(msg) {
let e = new Error(msg);
e.isUserError = true;
throw e;
}
Util.userError = userError;
// small deep equals for primitives, objects, arrays. returns error message
function deq(a, b) {
if (a === b)
return null;
if (!a || !b)
return "Null value";
if (typeof a == 'object' && typeof b == 'object') {
if (Array.isArray(a)) {
if (!Array.isArray(b)) {
return "Expected array";
}
if (a.length != b.length) {
return "Expected array of length " + a.length + ", got " + b.length;
}
for (let i = 0; i < a.length; i++) {
if (deq(a[i], b[i]) != null) {
return "Expected array value " + a[i] + " got " + b[i];
}
}
return null;
}
let ak = Object.keys(a);
let bk = Object.keys(a);
if (ak.length != bk.length) {
return "Expected " + ak.length + " keys, got " + bk.length;
}
for (let i = 0; i < ak.length; i++) {