<commit_before>import { Auth, MojangService, Net, ProfileService, Task, UserType } from '@xmcl/minecraft-launcher-core';
import fileType from 'file-type';
import got from 'got';
import fs from 'main/utils/vfs';
import { ComparableVersion } from 'maven-artifact-version';
import { basename, join } from 'path';
import base from 'universal/store/modules/user';
import { requireObject, requireString } from 'universal/utils/object';
import { parse as parseUrl } from 'url';
import { v4 } from 'uuid';

/**
 * @param {string} username
 */
function offline(username) {
    const prof = {
        id: v4(),
        name: username,
    };
    return {
        accessToken: v4(),
        clientToken: v4(),
        selectedProfile: prof,
        profiles: [prof],
        userId: v4(),
        properties: {},
        userType: UserType.Legacy,
    };
}

/**
 * The possible ways for user auth and profile:
 *
 * 1. By uuid
 * user login, use the the uuid from auth info to fetch game profile and get the skin;
 * this method usually requires the auth service and profile service is the same provider
 *
 * 2. By username
 * user login, and then use the username from auth info to fetch game profile and get the skin;
 * in this method, user could have different auth and profile services
 *
 * Module Requirement:
 *
 * login, by user name and password
 *
 * getGameProfile, get profile by user uuid or user name
 * selectGameProfileService, select the profile service
 *
 * @type {import('universal/store/modules/user').UserModule}
 */
const mod = {
    ...base,
    actions: {
        async save(context, { mutation }) {
            switch (mutation) {
                case 'addUserProfile':
                case 'setUserProfile':
                case 'updateGameProfile':
                case 'updateUserProfile':
                case 'authService':
                case 'profileService':
                case 'invalidateAuth':
                    await context.dispatch('setPersistence', {
                        path: 'user.json',
                        data: { ...context.state, cape: undefined, info: undefined, security: undefined, refreshingSecurity: undefined, refreshingSkin: undefined },
                    });
                    break;
                default:
            }
        },
        async load(context) {
            const data = await context.dispatch('getPersistence', { path: 'user.json', schema: 'UserConfig' });

            if (typeof data === 'object') {
                const authService = typeof data.authServices === 'object' ? data.authServices : {};
                authService.mojang = Auth.Yggdrasil.API_MOJANG;
                data.authServices = authService;

                const profileServices = typeof data.profileServices === 'object' ? data.profileServices : {};
                profileServices.mojang = ProfileService.API_MOJANG;
                data.profileServices = profileServices;

                delete data.info;
                delete data.refreshingSecurity;
                delete data.refreshingSkin;
                delete data.security;

                if (data.id) { // data fix
                    data.profiles = {
                        [data.userId]: {
                            id: data.userId,
                            type: data.userType,
                            account: '',
                            profileService: data.profileService,
                            authService: data.authService,
                            properties: data.properties,
                            profiles: [{ id: data.id, name: data.name }],
                        },
                    };
                    data.selectedUser = data.userId;
                    data.selectedGameProfile = data.id;
                }
                context.commit('userSnapshot', data);
            } else {
                console.log('Not found local user record, use default setting');
                context.commit('userSnapshot', {
                    authServices: {
                        mojang: Auth.Yggdrasil.API_MOJANG,
                    },
                    profileServices: {
                        mojang: ProfileService.API_MOJANG,
                    },
                    clientToken: v4().replace(/-/g, ''),
                    profiles: {},
                    selectedUser: '',
                    selectedUserProfile: '',
                    loginHistory: [],
                });
            }
        },
        async init(context) {
            if (!context.getters.offline) {
                context.dispatch('refreshUser');
            }
        },
        /**
         * Logout and clear current cache.
         */
        async logout(context) {
            const user = context.getters.selectedUser;
            if (context.getters.logined) {
                if (user.authService !== 'offline') {
                    await Auth.Yggdrasil.invalide({
                        accessToken: user.accessToken,
                        clientToken: context.state.clientToken,
                    }, context.getters.authService);
                }
            }
            context.commit('invalidateAuth');
        },

        async checkLocation(context) {
            if (!context.getters.logined) return true;
            const user = context.getters.selectedUser;
            if (user.authService !== 'mojang') return true;
            if (context.state.refreshingSecurity) return true;
            context.commit('refreshingSecurity', true);
            try {
                const result = await MojangService.checkLocation(user.accessToken);
                context.commit('userSecurity', result);
                return result;
            } catch (e) {
                if (e.error === 'ForbiddenOperationException' && e.errorMessage === 'Current IP is not secured') {
                    context.commit('userSecurity', false);
                    return false;
                }
                throw e;
            } finally {
                context.commit('refreshingSecurity', false);
            }
        },

        async getChallenges(context) {
            if (!context.getters.logined) return [];
            const user = context.getters.selectedUser;
            if (user.profileService !== 'mojang') return [];
            return MojangService.getChallenges(user.accessToken);
        },

        async submitChallenges(context, responses) {
            if (!context.getters.logined) throw new Error('Cannot submit challenge if not logined');
            const user = context.getters.selectedUser;
            if (user.authService !== 'mojang') throw new Error('Cannot sumit challenge if login mode is not mojang!');
            if (!(responses instanceof Array)) throw new Error('Expect responses Array!');
            const result = await MojangService.responseChallenges(user.accessToken, responses);
            context.commit('userSecurity', true);
            return result;
        },

        async refreshSkin(context) {
            const user = context.getters.selectedUser;
            const gameProfile = context.getters.selectedGameProfile;
            if (user.profileService === '') return;
            if (gameProfile.name === '') return;
            if (!context.getters.logined) return;
            if (context.state.refreshingSkin) return;

            context.commit('refreshingSkin', true);

            const { id, name } = gameProfile;

            try {
                let profile;
                if (context.getters.isServiceCompatible) {
                    profile = await ProfileService.fetch(id, { api: context.getters.profileService });
                } else {
                    profile = await ProfileService.lookup(name, { api: context.getters.profileService });
                    profile = await ProfileService.fetch(profile.id, { api: context.getters.profileService });
                }
                const textures = await ProfileService.getTextures(profile, false);
                const skin = textures.textures.SKIN;

                if (textures && skin) {
                    context.commit('updateGameProfile', { userId: user.id, profile: { ...gameProfile, textures: { ...textures.textures, SKIN: skin } } });
                }
            } catch (e) {
                console.warn(`Cannot refresh the skin data for user ${name}(${id}).`);
                console.warn(e);
                throw e;
            } finally {
                context.commit('refreshingSkin', false);
            }
        },

        async refreshInfo(context) {
            const user = context.getters.selectedUser;
            if (user.authService !== 'mojang') return;
            try {
                const info = await MojangService.getAccountInfo(user.accessToken);
                context.commit('mojangInfo', info);
            } catch (e) {
                console.warn(`Cannot refresh mojang info for user ${user.account}.`);
                console.warn(e);
                throw e;
            }
        },

        async uploadSkin(context, payload) {
            requireObject(payload);
            requireString(payload.data);

            const user = context.getters.selectedUser;
            const gameProfile = context.getters.selectedGameProfile;

            if (typeof payload.slim !== 'boolean') payload.slim = false;

            const { data, slim } = payload;
            let uri;
            if (typeof data === 'string') {
                uri = Buffer.from(data, 'base64');
            } else if (data instanceof Buffer) {
                uri = data;
            } else {
                throw new Error('Illegal Skin data format! Require a Buffer');
            }
            const accessToken = user.accessToken;
            const uuid = gameProfile.id;
            return ProfileService.setTexture({
                uuid,
                accessToken,
                type: 'skin',
                texture: {
                    metadata: {
                        model: slim ? 'slim' : 'steve',
                    },
                    url: '',
                },
                data: uri,
            }, context.getters.profileService).catch((e) => {
                console.error(e);
                throw e;
            });
        },
        async saveSkin(context, { skin, path }) {
            requireObject(skin);
            requireString(skin.data);
            requireString(path);
            return fs.writeFile(path, Buffer.from(skin.data, 'base64'));
        },
        async parseSkin(context, path) {
            requireString(path);

            const url = parseUrl(path);

            let buf;
            switch (url.protocol) {
                case 'http:':
                case 'https:':
                    buf = await got.get(path, { encoding: null }).then(r => r.body);
                    break;
                default:
                    buf = await fs.readFile(path);
            }
            const type = fileType(buf);
            if (type && type.ext === 'png') {
                return buf.toString('base64');
            }
            return undefined;
        },
        /**
         * Refresh the current user login status
         */
        async refreshUser(context) {
            if (!context.getters.logined) return;

            const user = context.getters.selectedUser;

            if (!context.getters.offline) {
                const validate = await Auth.Yggdrasil.validate({
                    accessToken: user.accessToken,
                    clientToken: context.state.clientToken,
                }, context.getters.authService);

                if (validate) {
                    context.dispatch('checkLocation');
                    return;
                }
                try {
                    const result = await Auth.Yggdrasil.refresh({
                        accessToken: user.accessToken,
                        clientToken: context.state.clientToken,
                    });
                    context.commit('updateUserProfile', result);
                    context.dispatch('checkLocation');
                    context.dispatch('refreshInfo').catch(_ => _);
                } catch (e) {
                    context.commit('invalidateAuth');
                }
            }

            context.dispatch('refreshSkin').catch(_ => _);
        },

        async switchUserProfile({ commit, dispatch }, payload) {
            commit('setUserProfile', payload);
            await dispatch('refreshUser');
        },

        /**
         * Login the user by current login mode. Refresh the skin and account information.
         */
        async login(context, payload) {
            if (!payload) throw new Error();
            requireObject(payload);
            requireString(payload.account);

            const selectedUserProfile = context.getters.selectedUser;

            const { account, password } = payload;
            const {
                authService = password ? 'mojang' : 'offline',
                profileService = 'mojang',
            } = payload;

            const usingAuthService = context.state.authServices[authService];

            try {
                /**
                 * @type {Auth}
                 */
                const result = authService === 'offline'
                    ? offline(account)
                    : await Auth.Yggdrasil.login({
                        username: account,
                        password,
                        clientToken: context.state.clientToken,
                    }, usingAuthService).catch((e) => {
                        if (e.message && e.message.startsWith('getaddrinfo ENOTFOUND')) {
                            const err = { message: 'error.internetNotConnected' };
                            throw err;
                        }
                        throw e;
                    });

                if (authService !== selectedUserProfile.authService
                    || profileService !== selectedUserProfile.profileService
                    || (authService === 'offline' && account !== selectedUserProfile.account)) {
                    context.commit('addUserProfile', {
                        account,
                        authService,
                        profileService,
                        id: result.userId,
                        type: result.userType,
                        accessToken: result.accessToken,
                        profiles: result.profiles.map(p => ({ ...p, textures: { SKIN: { url: '' } } })),
                    });

                    context.commit('setUserProfile', {
                        profileId: result.selectedProfile.id,
                        userId: result.userId,
                    });
                } else {
                    context.commit('updateUserProfile', result);
                }
                await context.dispatch('refreshSkin').catch(_ => _);
                await context.dispatch('refreshInfo').catch(_ => _);
            } catch (e) {
                console.error('Error during login.');
                console.error(e);
                throw e;
            }
        },
        async fetchAuthlibArtifacts(context) {
            const { body, statusCode, statusMessage } = await Net.fetchJson('https://authlib-injector.yushi.moe/artifacts.json');
            if (statusCode !== 200) throw new Error(statusMessage);
            return body;
        },
        async listAuthlibs(context) {
            const parent = context.rootGetters.path('authlibs');
            await fs.ensureDir(parent);
            const vers = await fs.readdir(parent);
            return vers.map(p => join(parent, p));
        },
        async ensureAuthlibInjection(context) {
            const task = Task.create('installAuthlibInjector', async (ctx) => {
                const parent = context.rootGetters.path('authlibs');
                await fs.ensureDir(parent);
                const existeds = await fs.readdir(parent);
                if (existeds.length !== 0) {
                    let greatest = existeds[0];
                    let greatestVer;
                    for (const f of existeds) {
                        const ver = new ComparableVersion(f.split('-')[2]);
                        if (!greatestVer) {
                            greatest = f;
                            greatestVer = ver;
                        } else if (ver.compareTo(greatestVer) > 0) {
                            greatest = f;
                            greatestVer = ver;
                        }
                    }
                    return join(parent, greatest);
                }
                const { body, statusCode, statusMessage } = await Net.fetchJson('https://authlib-injector.yushi.moe/artifact/latest.json');
                if (statusCode !== 200) throw new Error(statusMessage);
                const dest = context.rootGetters.path('authlibs', basename(body.download_url));
                await Net.downloadFileIfAbsentWork({
                    destination: dest,
                    url: body.download_url,
                    checksum: {
                        algorithm: 'sha256',
                        hash: body.checksums.sha256,
                    },
                })(ctx);
                return dest;
            });
            return context.dispatch('waitTask', await context.dispatch('executeTask', task));
        },
    },
};

export default mod;
<commit_msg>fix(user): refresh user problems after install authlib<commit_after>import { Auth, MojangService, Net, ProfileService, Task, UserType } from '@xmcl/minecraft-launcher-core';
import fileType from 'file-type';
import got from 'got';
import fs from 'main/utils/vfs';
import { ComparableVersion } from 'maven-artifact-version';
import { basename, join } from 'path';
import base from 'universal/store/modules/user';
import { requireObject, requireString } from 'universal/utils/object';
import { parse as parseUrl } from 'url';
import { v4 } from 'uuid';

/**
 * @param {string} username
 */
function offline(username) {
    const prof = {
        id: v4(),
        name: username,
    };
    return {
        accessToken: v4(),
        clientToken: v4(),
        selectedProfile: prof,
        profiles: [prof],
        userId: v4(),
        properties: {},
        userType: UserType.Legacy,
    };
}

/**
 * The possible ways for user auth and profile:
 *
 * 1. By uuid
 * user login, use the the uuid from auth info to fetch game profile and get the skin;
 * this method usually requires the auth service and profile service is the same provider
 *
 * 2. By username
 * user login, and then use the username from auth info to fetch game profile and get the skin;
 * in this method, user could have different auth and profile services
 *
 * Module Requirement:
 *
 * login, by user name and password
 *
 * getGameProfile, get profile by user uuid or user name
 * selectGameProfileService, select the profile service
 *
 * @type {import('universal/store/modules/user').UserModule}
 */
const mod = {
    ...base,
    actions: {
        async save(context, { mutation }) {
            switch (mutation) {
                case 'addUserProfile':
                case 'setUserProfile':
                case 'updateGameProfile':
                case 'updateUserProfile':
                case 'authService':
                case 'profileService':
                case 'invalidateAuth':
                    await context.dispatch('setPersistence', {
                        path: 'user.json',
                        data: { ...context.state, cape: undefined, info: undefined, security: undefined, refreshingSecurity: undefined, refreshingSkin: undefined },
                    });
                    break;
                default:
            }
        },
        async load(context) {
            const data = await context.dispatch('getPersistence', { path: 'user.json', schema: 'UserConfig' });

            if (typeof data === 'object') {
                const authService = typeof data.authServices === 'object' ? data.authServices : {};
                authService.mojang = Auth.Yggdrasil.API_MOJANG;
                data.authServices = authService;

                const profileServices = typeof data.profileServices === 'object' ? data.profileServices : {};
                profileServices.mojang = ProfileService.API_MOJANG;
                data.profileServices = profileServices;

                delete data.info;
                delete data.refreshingSecurity;
                delete data.refreshingSkin;
                delete data.security;

                if (data.id) { // data fix
                    data.profiles = {
                        [data.userId]: {
                            id: data.userId,
                            type: data.userType,
                            account: '',
                            profileService: data.profileService,
                            authService: data.authService,
                            properties: data.properties,
                            profiles: [{ id: data.id, name: data.name }],
                        },
                    };
                    data.selectedUser = data.userId;
                    data.selectedGameProfile = data.id;
                }
                context.commit('userSnapshot', data);
            } else {
                console.log('Not found local user record, use default setting');
                context.commit('userSnapshot', {
                    authServices: {
                        mojang: Auth.Yggdrasil.API_MOJANG,
                    },
                    profileServices: {
                        mojang: ProfileService.API_MOJANG,
                    },
                    clientToken: v4().replace(/-/g, ''),
                    profiles: {},
                    selectedUser: '',
                    selectedUserProfile: '',
                    loginHistory: [],
                });
            }
        },
        async init(context) {
            if (!context.getters.offline) {
                context.dispatch('refreshUser');
            }
        },
        /**
         * Logout and clear current cache.
         */
        async logout(context) {
            const user = context.getters.selectedUser;
            if (context.getters.logined) {
                if (user.authService !== 'offline') {
                    await Auth.Yggdrasil.invalide({
                        accessToken: user.accessToken,
                        clientToken: context.state.clientToken,
                    }, context.getters.authService);
                }
            }
            context.commit('invalidateAuth');
        },

        async checkLocation(context) {
            if (!context.getters.logined) return true;
            const user = context.getters.selectedUser;
            if (user.authService !== 'mojang') return true;
            if (context.state.refreshingSecurity) return true;
            context.commit('refreshingSecurity', true);
            try {
                const result = await MojangService.checkLocation(user.accessToken);
                context.commit('userSecurity', result);
                return result;
            } catch (e) {
                if (e.error === 'ForbiddenOperationException' && e.errorMessage === 'Current IP is not secured') {
                    context.commit('userSecurity', false);
                    return false;
                }
                throw e;
            } finally {
                context.commit('refreshingSecurity', false);
            }
        },

        async getChallenges(context) {
            if (!context.getters.logined) return [];
            const user = context.getters.selectedUser;
            if (user.profileService !== 'mojang') return [];
            return MojangService.getChallenges(user.accessToken);
        },

        async submitChallenges(context, responses) {
            if (!context.getters.logined) throw new Error('Cannot submit challenge if not logined');
            const user = context.getters.selectedUser;
            if (user.authService !== 'mojang') throw new Error('Cannot sumit challenge if login mode is not mojang!');
            if (!(responses instanceof Array)) throw new Error('Expect responses Array!');
            const result = await MojangService.responseChallenges(user.accessToken, responses);
            context.commit('userSecurity', true);
            return result;
        },

        async refreshSkin(context) {
            const user = context.getters.selectedUser;
            const gameProfile = context.getters.selectedGameProfile;
            if (user.profileService === '') return;
            if (gameProfile.name === '') return;
            if (!context.getters.logined) return;
            if (context.state.refreshingSkin) return;

            context.commit('refreshingSkin', true);

            const { id, name } = gameProfile;

            try {
                let profile;
                if (context.getters.isServiceCompatible) {
                    profile = await ProfileService.fetch(id, { api: context.getters.profileService });
                } else {
                    profile = await ProfileService.lookup(name, { api: context.getters.profileService });
                    profile = await ProfileService.fetch(profile.id, { api: context.getters.profileService });
                }
                const textures = await ProfileService.getTextures(profile, false);
                const skin = textures.textures.SKIN;

                if (textures && skin) {
                    context.commit('updateGameProfile', { userId: user.id, profile: { ...gameProfile, textures: { ...textures.textures, SKIN: skin } } });
                }
            } catch (e) {
                console.warn(`Cannot refresh the skin data for user ${name}(${id}).`);
                console.warn(e);
                throw e;
            } finally {
                context.commit('refreshingSkin', false);
            }
        },

        async refreshInfo(context) {
            const user = context.getters.selectedUser;
            if (user.authService !== 'mojang') return;
            try {
                const info = await MojangService.getAccountInfo(user.accessToken);
                context.commit('mojangInfo', info);
            } catch (e) {
                console.warn(`Cannot refresh mojang info for user ${user.account}.`);
                console.warn(e);
                throw e;
            }
        },

        async uploadSkin(context, payload) {
            requireObject(payload);
            requireString(payload.data);

            const user = context.getters.selectedUser;
            const gameProfile = context.getters.selectedGameProfile;

            if (typeof payload.slim !== 'boolean') payload.slim = false;

            const { data, slim } = payload;
            let uri;
            if (typeof data === 'string') {
                uri = Buffer.from(data, 'base64');
            } else if (data instanceof Buffer) {
                uri = data;
            } else {
                throw new Error('Illegal Skin data format! Require a Buffer');
            }
            const accessToken = user.accessToken;
            const uuid = gameProfile.id;
            return ProfileService.setTexture({
                uuid,
                accessToken,
                type: 'skin',
                texture: {
                    metadata: {
                        model: slim ? 'slim' : 'steve',
                    },
                    url: '',
                },
                data: uri,
            }, context.getters.profileService).catch((e) => {
                console.error(e);
                throw e;
            });
        },
        async saveSkin(context, { skin, path }) {
            requireObject(skin);
            requireString(skin.data);
            requireString(path);
            return fs.writeFile(path, Buffer.from(skin.data, 'base64'));
        },
        async parseSkin(context, path) {
            requireString(path);

            const url = parseUrl(path);

            let buf;
            switch (url.protocol) {
                case 'http:':
                case 'https:':
                    buf = await got.get(path, { encoding: null }).then(r => r.body);
                    break;
                default:
                    buf = await fs.readFile(path);
            }
            const type = fileType(buf);
            if (type && type.ext === 'png') {
                return buf.toString('base64');
            }
            return undefined;
        },
        /**
         * Refresh the current user login status
         */
        async refreshUser(context) {
            if (!context.getters.logined) return;

            const user = context.getters.selectedUser;

            if (!context.getters.offline) {
                const validate = await Auth.Yggdrasil.validate({
                    accessToken: user.accessToken,
                    clientToken: context.state.clientToken,
                }, context.getters.authService);

                if (validate) {
                    context.dispatch('checkLocation');
                    return;
                }
                try {
                    const result = await Auth.Yggdrasil.refresh({
                        accessToken: user.accessToken,
                        clientToken: context.state.clientToken,
                    });
                    context.commit('updateUserProfile', result);
                    context.dispatch('checkLocation');
                    context.dispatch('refreshInfo').catch(_ => _);
                } catch (e) {
                    context.commit('invalidateAuth');
                }
            }

            context.dispatch('refreshSkin').catch(_ => _);
        },

        async switchUserProfile({ commit, dispatch }, payload) {
            commit('setUserProfile', payload);
            await dispatch('refreshUser');
        },

        /**
         * Login the user by current login mode. Refresh the skin and account information.
         */
        async login(context, payload) {
            if (!payload) throw new Error();
            requireObject(payload);
            requireString(payload.account);

            const selectedUserProfile = context.getters.selectedUser;

            const { account, password } = payload;
            const {
                authService = password ? 'mojang' : 'offline',
                profileService = 'mojang',
            } = payload;

            const usingAuthService = context.state.authServices[authService];

            try {
                /**
                 * @type {Auth}
                 */
                const result = authService === 'offline'
                    ? offline(account)
                    : await Auth.Yggdrasil.login({
                        username: account,
                        password,
                        clientToken: context.state.clientToken,
                    }, usingAuthService).catch((e) => {
                        if (e.message && e.message.startsWith('getaddrinfo ENOTFOUND')) {
                            const err = { message: 'error.internetNotConnected' };
                            throw err;
                        }
                        throw e;
                    });

                if (authService !== selectedUserProfile.authService
                    || profileService !== selectedUserProfile.profileService
                    || (authService === 'offline' && account !== selectedUserProfile.account)) {
                    context.commit('addUserProfile', {
                        account,
                        authService,
                        profileService,
                        id: result.userId,
                        type: result.userType,
                        accessToken: result.accessToken,
                        profiles: result.profiles.map(p => ({ ...p, textures: { SKIN: { url: '' } } })),
                    });

                    context.commit('setUserProfile', {
                        profileId: result.selectedProfile.id,
                        userId: result.userId,
                    });
                } else {
                    context.commit('updateUserProfile', result);
                }
                await context.dispatch('refreshSkin').catch(_ => _);
                await context.dispatch('refreshInfo').catch(_ => _);
            } catch (e) {
                console.error('Error during login.');
                console.error(e);
                throw e;
            }
        },
        async fetchAuthlibArtifacts(context) {
            const { body, statusCode, statusMessage } = await Net.fetchJson('https://authlib-injector.yushi.moe/artifacts.json');
            if (statusCode !== 200) throw new Error(statusMessage);
            return body;
        },
        async listAuthlibs(context) {
            const parent = context.rootGetters.path('authlibs');
            await fs.ensureDir(parent);
            const vers = await fs.readdir(parent);
            return vers.map(p => join(parent, p));
        },
        async ensureAuthlibInjection(context) {
            const task = Task.create('installAuthlibInjector', async (ctx) => {
                const parent = context.rootGetters.path('authlibs');
                await fs.ensureDir(parent);
                const existeds = await fs.readdir(parent);
                if (existeds.length !== 0) {
                    let greatest = existeds[0];
                    let greatestVer;
                    for (const f of existeds) {
                        const ver = new ComparableVersion(f.split('-')[2]);
                        if (!greatestVer) {
                            greatest = f;
                            greatestVer = ver;
                        } else if (ver.compareTo(greatestVer) > 0) {
                            greatest = f;
                            greatestVer = ver;
                        }
                    }
                    return join(parent, greatest);
                }
                const { body, statusCode, statusMessage } = await Net.fetchJson('https://authlib-injector.yushi.moe/artifact/latest.json');
                if (statusCode !== 200) throw new Error(statusMessage);
                const dest = context.rootGetters.path('authlibs', basename(body.download_url));
                await Net.downloadFileIfAbsentWork({
                    destination: dest,
                    url: body.download_url,
                    checksum: {
                        algorithm: 'sha256',
                        hash: body.checksums.sha256,
                    },
                })(ctx);
                return dest;
            });
            await context.dispatch('waitTask', await context.dispatch('executeTask', task));
            await context.dispatch('diagnoseUser');
        },
    },
};

export default mod;
<|endoftext|>


<commit_before>var Sails = require('sails').Sails;

describe('sails-hook-cron', function () {
  var sails;

  before(function (done) {
    this.timeout(10000);

    Sails().lift({
      hooks: {
        "cron": require('../../'),
        "grunt": false
      }
    }, function (error, _sails) {
      if (error) return done(error);
      sails = _sails;
      return done();
    });
  });

  after(function (done) {
    return sails ? sails.lower(done) : done();
  });

  it('sails does not crash', function () {
    return true;
  });
});
<commit_msg>Add default 2 cron tasks to tests<commit_after>var assert = require('chai').assert;
var Sails = require('sails').Sails;

describe('sails-hook-cron::main', function () {
  var sails;

  before(function (done) {
    this.timeout(10000);

    Sails().lift({
      cron: {
        '* * * * * 1': console.log,
        '* * * * * 2': {
          onTick: console.log,
          onComplete: console.log,
          timezone: 'Europe/Kiev'
        }
      },
      hooks: {
        "cron": require('../../'),
        "csrf": false,
        "grunt": false,
        "i18n": false,
        "pubsub": false,
        "session": false,
        "views": false
      }
    }, function (error, _sails) {
      if (error) return done(error);
      sails = _sails;
      return done();
    });
  });

  after(function (done) {
    return sails ? sails.lower(done) : done();
  });

  it('Should properly load cron hook', function () {
    assert.isObject(sails.config.cron);
    assert.isObject(sails.hooks.cron);
  });

  it('Should properly load cron task with function declaration', function () {
  });
});
<|endoftext|>


<commit_before>/**
 * Created by zarges on 23/06/15.
 */
'use strict';
angular.module('mwResponseHandler', [])

  .provider('ResponseHandler', function () {

    var _routeHandlersPerMethodContainer = {
        POST: [],
        PUT: [],
        GET: [],
        DELETE: [],
        PATCH: []
      };

    var _methodIsInValidError = function(method){
      return new Error('Method '+method+' is invalid. Valid methods are POST, PUT, GET, DELETE, PATCH');
    };

    var RouteHandler = function (route) {

      var _codes = {
          ERROR: [],
          SUCCESS: []
        },
        _route = route,
        _routeRegex = null,
        _optionalParam = /\((.*?)\)/g,
        _namedParam = /(\(\?)?:\w+/g,
        _splatParam = /\*\w?/g,
        _escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;

      var _routeToRegExp = function (route) {
        route = route.replace(_escapeRegExp, '\\$&')
          .replace(_optionalParam, '(?:$1)?')
          .replace(_namedParam, function (match, optional) {
            return optional ? match : '([^/?]+)';
          })
          .replace(_splatParam, '([^?]*?)');
        return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
      };

      var _registerCallbackForCode = function (code, callback) {

        var existingCallbacks = _codes[code],
          callbacks = existingCallbacks || [];

        callbacks.push(callback);

        _codes[code] = callbacks;
      };

      var _getCallbackForCode = function (code) {
        return _codes[code];
      };

      this.matchesUrl = function (url) {
        return url.match(_routeRegex);
      };

      this.registerCallbackForStatusCodes = function (statusCodes, callback) {
        statusCodes.forEach(function (statusCode) {
          _registerCallbackForCode(statusCode, callback);
        }, this);
      };

      this.registerCallbackForSuccess = function (callback) {
        _registerCallbackForCode('SUCCESS', callback);
      };

      this.registerCallbackForError = function (callback) {
        _registerCallbackForCode('ERROR', callback);
      };

      this.getCallbacksForStatusCode = function (statusCode) {
        return _getCallbackForCode(statusCode);
      };

      this.getCallbacksForSuccess = function () {
        return _getCallbackForCode('SUCCESS');
      };

      this.getCallbacksForError = function () {
        return _getCallbackForCode('ERROR');
      };

      var main = function () {
        _routeRegex = _routeToRegExp(_route);
      };

      main.call(this);
    };

    this.registerAction = function (route, callback, options) {
      options = options || {};

      if(!options.onError && !options.onSuccess && !options.statusCodes){
        throw new Error('You have to specify either some statusCodes or set onSuccess or onError to true in the options parameter object');
      }

      if (( options.onError && options.onSuccess ) || ( (options.onError || options.onSuccess) && options.statusCodes )) {
        throw new Error('Definition is too imprecise');
      }
      if (!options.method && !options.methods) {
        throw new Error('Method has to be defined in options e.g method: "POST" or methods:["POST"]');
      }

      options.methods = options.methods || [options.method];

      options.methods.forEach(function(method){

        if (!_routeHandlersPerMethodContainer[method]) {
          throw _methodIsInValidError(method);
        }

        var existingRouteHandlerContainer = _.findWhere(_routeHandlersPerMethodContainer[method], {id: route}),
          routeHandlerContainer = existingRouteHandlerContainer || {id: route, handler: new RouteHandler(route)},
          routeHandler = routeHandlerContainer.handler;

        if (options.statusCodes) {
          routeHandler.registerCallbackForStatusCodes(options.statusCodes, callback);
        } else if (options.onSuccess) {
          routeHandler.registerCallbackForSuccess(callback);
        } else if (options.onError) {
          routeHandler.registerCallbackForError(callback);
        }

        if (!existingRouteHandlerContainer) {
          _routeHandlersPerMethodContainer[method].push(routeHandlerContainer);
        }

      });
    };

    this.registerSuccessAction = function (route, callback, method) {
      return this.registerAction(route, callback, {
        method: method,
        onSuccess: true
      });
    };

    this.registerErrorAction = function (route, callback, method) {
      return this.registerAction(route, callback, {
        method: method,
        onError: true
      });
    };

    this.registerDefaultAction = function(callback, options){
      options = options || {};
      return this.registerAction('*', callback, options);
    };

    this.registerDefaultSuccessAction = function (callback, method) {
      return this.registerAction('*', callback, {
        method: method,
        onSuccess: true
      });
    };

    this.registerDefaultErrorAction = function (callback, method) {
      return this.registerAction('*', callback, {
        method: method,
        onError: true
      });
    };

    this.$get = function ($injector) {

      var _executeCallback = function (callbacks, response) {
        callbacks.forEach(function (callback) {
          callback = angular.isString(callback) ? $injector.get(callback) : callback;
          callback.call(this, response);
        }, this);
      };

      return {
        getHandlerForUrlAndCode: function (method, url, statusCode, isError) {
          var _returnHandler,
            _allReturnHandlersForRoute = [];

          if (!_routeHandlersPerMethodContainer[method]) {
            throw _methodIsInValidError(method);
          }

          _routeHandlersPerMethodContainer[method].forEach(function (routeHandlerContainer) {
            var handler = routeHandlerContainer.handler;
            if (handler.matchesUrl(url)) {
              _allReturnHandlersForRoute.push(handler);
            }
          });

          _allReturnHandlersForRoute.forEach(function(handler){
            var callback = [];
            if(_returnHandler){
              return;
            }
            callback = handler.getCallbacksForStatusCode(statusCode);
            if (!callback && isError) {
              callback = handler.getCallbacksForError();
            }
            if (!callback){
              callback = handler.getCallbacksForSuccess();
            }
            if(!_.isEmpty(callback)){
              _returnHandler = handler;
            }
          });

          return _returnHandler;
        },
        handle: function (response, isError) {
          var url = response.config.url,
            method = response.config.method,
            statusCode = response.status,
            handler = this.getHandlerForUrlAndCode(method, url, statusCode, isError);

          if (handler) {
            var statusCodeCallback = handler.getCallbacksForStatusCode(statusCode);

            if (statusCodeCallback) {
              _executeCallback(statusCodeCallback, response);
            } else if (isError) {
              var isErrorCallback = handler.getCallbacksForError();
              _executeCallback(isErrorCallback, response);
            } else {
              var isSuccessCallback = handler.getCallbacksForSuccess();
              _executeCallback(isSuccessCallback, response);
            }
          }
        }
      };
    };
  })

  .config(function ($provide, $httpProvider) {

    $provide.factory('requestInterceptorForHandling', function ($q, ResponseHandler) {
      return {
        response: function (response) {
          ResponseHandler.handle(response, false);
          return response;
        },
        responseError: function (response) {
          ResponseHandler.handle(response, true);
          return $q.reject(response);
        }
      };
    });

    $httpProvider.interceptors.push('requestInterceptorForHandling');

  });<commit_msg>simplyfies code<commit_after>/**
 * Created by zarges on 23/06/15.
 */
'use strict';
angular.module('mwResponseHandler', [])

  .provider('ResponseHandler', function () {

    var _routeHandlersPerMethodContainer = {
        POST: [],
        PUT: [],
        GET: [],
        DELETE: [],
        PATCH: []
      };

    var _methodIsInValidError = function(method){
      return new Error('Method '+method+' is invalid. Valid methods are POST, PUT, GET, DELETE, PATCH');
    };

    var RouteHandler = function (route) {

      var _codes = {
          ERROR: [],
          SUCCESS: []
        },
        _route = route,
        _routeRegex = null,
        _optionalParam = /\((.*?)\)/g,
        _namedParam = /(\(\?)?:\w+/g,
        _splatParam = /\*\w?/g,
        _escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;

      var _routeToRegExp = function (route) {
        route = route.replace(_escapeRegExp, '\\$&')
          .replace(_optionalParam, '(?:$1)?')
          .replace(_namedParam, function (match, optional) {
            return optional ? match : '([^/?]+)';
          })
          .replace(_splatParam, '([^?]*?)');
        return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
      };

      var _registerCallbackForCode = function (code, callback) {

        var existingCallbacks = _codes[code],
          callbacks = existingCallbacks || [];

        callbacks.push(callback);

        _codes[code] = callbacks;
      };

      var _getCallbackForCode = function (code) {
        return _codes[code];
      };

      this.matchesUrl = function (url) {
        return url.match(_routeRegex);
      };

      this.registerCallbackForStatusCodes = function (statusCodes, callback) {
        statusCodes.forEach(function (statusCode) {
          _registerCallbackForCode(statusCode, callback);
        }, this);
      };

      this.registerCallbackForSuccess = function (callback) {
        _registerCallbackForCode('SUCCESS', callback);
      };

      this.registerCallbackForError = function (callback) {
        _registerCallbackForCode('ERROR', callback);
      };

      this.getCallbacksForStatusCode = function (statusCode) {
        return _getCallbackForCode(statusCode);
      };

      this.getCallbacksForSuccess = function () {
        return _getCallbackForCode('SUCCESS');
      };

      this.getCallbacksForError = function () {
        return _getCallbackForCode('ERROR');
      };

      var main = function () {
        _routeRegex = _routeToRegExp(_route);
      };

      main.call(this);
    };

    this.registerAction = function (route, callback, options) {
      options = options || {};

      if(!options.onError && !options.onSuccess && !options.statusCodes){
        throw new Error('You have to specify either some statusCodes or set onSuccess or onError to true in the options parameter object');
      }

      if (( options.onError && options.onSuccess ) || ( (options.onError || options.onSuccess) && options.statusCodes )) {
        throw new Error('Definition is too imprecise');
      }
      if (!options.method && !options.methods) {
        throw new Error('Method has to be defined in options e.g method: "POST" or methods:["POST"]');
      }

      options.methods = options.methods || [options.method];

      options.methods.forEach(function(method){

        if (!_routeHandlersPerMethodContainer[method]) {
          throw _methodIsInValidError(method);
        }

        var existingRouteHandlerContainer = _.findWhere(_routeHandlersPerMethodContainer[method], {id: route}),
          routeHandlerContainer = existingRouteHandlerContainer || {id: route, handler: new RouteHandler(route)},
          routeHandler = routeHandlerContainer.handler;

        if (options.statusCodes) {
          routeHandler.registerCallbackForStatusCodes(options.statusCodes, callback);
        } else if (options.onSuccess) {
          routeHandler.registerCallbackForSuccess(callback);
        } else if (options.onError) {
          routeHandler.registerCallbackForError(callback);
        }

        if (!existingRouteHandlerContainer) {
          _routeHandlersPerMethodContainer[method].push(routeHandlerContainer);
        }

      });
    };

    this.registerSuccessAction = function (route, callback, method) {
      return this.registerAction(route, callback, {
        method: method,
        onSuccess: true
      });
    };

    this.registerErrorAction = function (route, callback, method) {
      return this.registerAction(route, callback, {
        method: method,
        onError: true
      });
    };

    this.registerDefaultAction = function(callback, options){
      options = options || {};
      return this.registerAction('*', callback, options);
    };

    this.registerDefaultSuccessAction = function (callback, method) {
      return this.registerAction('*', callback, {
        method: method,
        onSuccess: true
      });
    };

    this.registerDefaultErrorAction = function (callback, method) {
      return this.registerAction('*', callback, {
        method: method,
        onError: true
      });
    };

    this.$get = function ($injector) {

      var _executeCallback = function (callbacks, response) {
        callbacks.forEach(function (callback) {
          callback = angular.isString(callback) ? $injector.get(callback) : callback;
          callback.call(this, response);
        }, this);
      };

      var _isHandlerDefinedFor = function(handler, statusCode, isError){
        return !!(
          handler.getCallbacksForStatusCode(statusCode) ||
          isError && handler.getCallbacksForError() ||
          !isError && handler.getCallbacksForSuccess()
        );
      };

      return {
        getHandlerForUrlAndCode: function (method, url, statusCode, isError) {
          var _returnHandler;

          if (!_routeHandlersPerMethodContainer[method]) {
            throw _methodIsInValidError(method);
          }

          _routeHandlersPerMethodContainer[method].forEach(function (routeHandlerContainer) {
            var handler = routeHandlerContainer.handler;
            if (handler.matchesUrl(url) && _isHandlerDefinedFor(handler, statusCode, isError)) {
              _returnHandler = handler;
            }
          });

          return _returnHandler;
        },
        handle: function (response, isError) {
          var url = response.config.url,
            method = response.config.method,
            statusCode = response.status,
            handler = this.getHandlerForUrlAndCode(method, url, statusCode, isError);

          if (handler) {
            var statusCodeCallback = handler.getCallbacksForStatusCode(statusCode);

            if (statusCodeCallback) {
              _executeCallback(statusCodeCallback, response);
            } else if (isError) {
              var isErrorCallback = handler.getCallbacksForError();
              _executeCallback(isErrorCallback, response);
            } else {
              var isSuccessCallback = handler.getCallbacksForSuccess();
              _executeCallback(isSuccessCallback, response);
            }
          }
        }
      };
    };
  })

  .config(function ($provide, $httpProvider) {

    $provide.factory('requestInterceptorForHandling', function ($q, ResponseHandler) {
      return {
        response: function (response) {
          ResponseHandler.handle(response, false);
          return response;
        },
        responseError: function (response) {
          ResponseHandler.handle(response, true);
          return $q.reject(response);
        }
      };
    });

    $httpProvider.interceptors.push('requestInterceptorForHandling');

  });<|endoftext|>


<commit_before>"use strict";

var chai = require('chai'),
    expect = chai.expect,
    sinon = require('sinon'),
    proxyquire = require('proxyquire'),
    senderPath = '../../lib/sender',
    Constants = require('../../lib/constants'),
    Message = require('../../lib/message');

describe('UNIT Sender', function () {
  // Use object to set arguments passed into callback
  var args = {};
  var requestStub = function (options, callback) {
    args.options = options;
    return callback( args.err, args.res, args.resBody );
  };
  var Sender = proxyquire(senderPath, { 'request': requestStub });

  describe('constructor', function () {
    var Sender = require(senderPath);

    it('should call new on constructor if user does not', function () {
      var sender = Sender();
      expect(sender).to.not.be.undefined;
      expect(sender).to.be.instanceOf(Sender);
    });

    it('should create a Sender with key and options passed in', function () {
      var options = {
        proxy: 'http://myproxy.com',
        maxSockets: 100,
        timeout: 100
      };
      var key = 'myAPIKey',
          sender = new Sender(key, options);
      expect(sender).to.be.instanceOf(Sender);
      expect(sender.key).to.equal(key);
      expect(sender.options).to.deep.equal(options);
    });

    it.skip('should do something if not passed a valid key');
  });

  describe('sendNoRetry()', function () {
    // Set arguments passed in request proxy
    function setArgs(err, res, resBody) {
      args = {
        err: err,
        res: res,
        resBody: resBody
      };
    };

    it('should set proxy, maxSockets, and/or timeout of req object if passed into constructor', function () {
      var options = {
        proxy: 'http://myproxy.com',
        maxSockets: 100,
        timeout: 1000
      };
      var sender = new Sender('mykey', options);
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', function () {});
      expect(args.options.proxy).to.equal(options.proxy);
      expect(args.options.maxSockets).to.equal(options.maxSockets);
      expect(args.options.timeout).to.equal(options.timeout);
    });

    it('should set the API key of req object if passed in API key', function () {
      var sender = new Sender('myKey');
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', function () {});
      expect(args.options.headers.Authorization).to.equal('key=myKey');
    });

    it('should stringify body of req before it is sent', function () {
      var sender = new Sender('mykey');
      var m = new Message({ collapseKey: 'Message', data: {} });
      sender.sendNoRetry(m, '', function () {});
      expect(args.options.body).to.be.a('string');
    });

    it('should set properties of body with message properties', function () {
      var mess = new Message({
        delayWhileIdle: true,
        collapseKey: 'Message',
        timeToLive: 100,
        dryRun: true,
        data: {
          name: 'Matt'
        }
      });
      var sender = new Sender('mykey');
      sender.sendNoRetry(mess, '', function () {});
      var body = JSON.parse(args.options.body);
      expect(body[Constants.PARAM_DELAY_WHILE_IDLE]).to.equal(mess.delayWhileIdle);
      expect(body[Constants.PARAM_COLLAPSE_KEY]).to.equal(mess.collapseKey);
      expect(body[Constants.PARAM_TIME_TO_LIVE]).to.equal(mess.timeToLive);
      expect(body[Constants.PARAM_DRY_RUN]).to.equal(mess.dryRun);
      expect(body[Constants.PARAM_PAYLOAD_KEY]).to.deep.equal(mess.data);
    });

    it('should set the registration ids to reg ids passed in', function () {
      var sender = new Sender('myKey');
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, ["registration id 1", "registration id 2"], function () {});
      var body = JSON.parse(args.options.body);
      expect(body[Constants.JSON_REGISTRATION_IDS]).to.deep.equal(["registration id 1", "registration id 2"]);
    });

    it('should pass an error into callback if request returns an error', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs('an error', {}, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.calledWith('an error')).to.be.ok;
    });

    it('should pass an error into callback if response does not exist', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, undefined, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.be.a('string');
    });

    it('should return the status code as an error if returned a 500', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 500 }, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(500);
    });

    it('should return the status code as an error if returned a 401', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 401 }, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(401);
    });

    it('should return the status code as an error if returned a 400', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 400 }, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(400);
    });

    it('should pass an error into the callback if resBody cannot be parsed', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 200 }, "non-JSON string.");
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.eq('Error parsing GCM response');
    });

    it('should pass in parsed resBody into callback on success', function () {
      var callback = sinon.spy();
      var resBody = {
        message: 'woohoo!',
        success: true
      };
      var sender = new Sender('myKey');
      setArgs(null, { statusCode: 200 }, JSON.stringify(resBody));
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][1]).to.deep.equal(resBody);
    });
  });

  describe('send()', function () {
    var restore = {},
        backoff = Constants.BACKOFF_INITIAL_DELAY;
    // Set args passed into sendNoRetry
    function setArgs(err, result) {
      args = {
        err: err,
        result: result,
        tries: 0
      };
    };

    before( function () {
      restore.sendNoRetry = Sender.prototype.sendNoRetry;
      Sender.prototype.sendNoRetry = function (message, reg_ids, callback) {
        console.log('Firing send');
        args.message = message;
        args.reg_ids = reg_ids;
        args.tries++;
        var nextResult;
        if(!args.result) {
          nextResult = args.result;
        }
        else if(args.result.length > 1) {
          nextResult = args.result.slice(0,1)[0];
          args.result = args.result.slice(1,args.result.length);
        }
        else if(args.result.length == 1) {
          args.result = args.result[0];
          nextResult = args.result;
        }
        else {
          nextResult = args.result;
        }
        callback( args.err, nextResult );
      };
    });

    after( function () {
      Sender.prototype.sendNoRetry = restore.sendNoRetry;
    });

    it.skip('should do something if passed not an array for regIds');

    it('should pass an error into callback if array has no regIds', function (done) {
      var callback = function(error) {
        expect(error).to.be.a('string');
        done();
      };
      var sender = new Sender('myKey');
      sender.send({}, [], 0, callback);
    });

    it('should pass the message and the regId to sendNoRetry on call', function () {
      var sender = new Sender('myKey'),
          message = { data: {} },
          regId = [24];
      setArgs(null, {});
      sender.send(message, regId, 0, function () {});
      expect(args.message).to.equal(message);
      expect(args.reg_ids).to.equal(regId);
      expect(args.tries).to.equal(1);
    });

    it('should pass the message and the regIds to sendNoRetry on call', function () {
      var sender = new Sender('myKey'),
          message = { data: {} },
          regIds = [24, 34, 44];
      setArgs(null, {});
      sender.send(message, regIds, 0, function () {});
      expect(args.message).to.equal(message);
      expect(args.reg_ids).to.equal(regIds);
      expect(args.tries).to.equal(1);
    });

    it('should pass the result into callback if successful for id', function () {
      var callback = sinon.spy(),
          result = { success: true },
          sender = new Sender('myKey');
      setArgs(null, result);
      sender.send({}, [1], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][1]).to.equal(result);
      expect(args.tries).to.equal(1);
    });

    it('should pass the result into callback if successful for ids', function () {
      var callback = sinon.spy(),
          result = { success: true },
          sender = new Sender('myKey');
      setArgs(null, result);
      sender.send({}, [1, 2, 3], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][1]).to.equal(result);
      expect(args.tries).to.equal(1);
    });

    it('should pass the error into callback if failure and no retry for id', function () {
      var callback = sinon.spy(),
          error = 'my error',
          sender = new Sender('myKey');
      setArgs(error);
      sender.send({}, [1], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(error);
      expect(args.tries).to.equal(1);
    });

    it('should pass the error into callback if failure and no retry for ids', function () {
      var callback = sinon.spy(),
          error = 'my error',
          sender = new Sender('myKey');
      setArgs(error);
      sender.send({}, [1, 2, 3], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(error);
      expect(args.tries).to.equal(1);
    });

    it('should retry number of times passed into call and do exponential backoff', function (done) {
      var start = new Date();
      var callback = function () {
        expect(args.tries).to.equal(2);
        expect(new Date() - start).to.be.gte(Math.pow(2, 0) * backoff);
        done();
      };
      var sender = new Sender('myKey');
      setArgs('my error');
      sender.send({ data: {}}, [1], 1, callback);
    });

    it('should retry if not all regIds were successfully sent', function (done) {
      var callback = function () {
        expect(args.tries).to.equal(3);
        // Last call of sendNoRetry should be for only failed regIds
        expect(args.reg_ids.length).to.equal(1);
        expect(args.reg_ids[0]).to.equal(3);
        done();
      };
      var sender = new Sender('myKey');
      setArgs(null, [{ results: [{}, { error: 'Unavailable' }, { error: 'Unavailable' }]}, { results: [ {}, { error: 'Unavailable' } ] }, { results: [ {} ] } ]);
      sender.send({ data: {}}, [1,2,3], {
        retries: 5,
        backoff: 100
      }, callback);
    });

    it('should retry all regIds in event of an error', function (done) {
      var start = new Date();
      var callback = function () {
        expect(args.tries).to.equal(2);
        expect(args.reg_ids.length).to.equal(3);
        done();
      };
      var sender = new Sender('myKey');
      setArgs('my error');
      sender.send({ data: {}}, [1,2,3], 1, callback);
    });

    it('should update the failures and successes correctly when retrying', function (done) {
      var callback = function(error, result) {
        expect(error).to.equal(null);
        expect(result.canonical_ids).to.equal(1);
        expect(result.success).to.equal(2);
        expect(result.failure).to.equal(0);
        done();
      };
      var sender = new Sender('myKey');
      setArgs(null, [
        { success: 1, failure: 2, canonical_ids: 0, results: [ {}, { error: 'Unavailable' }, { error: 'Unavailable' } ] },
        { success: 1, canonical_ids: 1, failure: 0, results: [ {}, {} ] }
      ]);
      sender.send({ data: {}}, [1,2,3], 3, callback);
    });

    it('should update the failures and successes correctly when retrying and failing some', function (done) {
      var callback = function(error, result) {
        expect(error).to.equal(null);
        expect(result.canonical_ids).to.equal(0);
        expect(result.success).to.equal(1);
        expect(result.failure).to.equal(2);
        done();
      };
      var sender = new Sender('myKey');
      setArgs(null, [
        { success: 0, failure: 3, canonical_ids: 0, results: [ { error: 'Unavailable' }, { error: 'Unavailable' }, { error: 'Unavailable' } ] },
        { success: 1, canonical_ids: 0, failure: 2, results: [ { error: 'Unavailable' }, { error: 'Unavailable' }, {} ] },
        { success: 0, canonical_ids: 0, failure: 2, results: [ { error: 'Unavailable' }, { error: 'Unavailable' } ] }
      ]);
      sender.send({ data: {}}, [1,2,3], {
        retries: 3,
        backoff: 100
      }, callback);
    });
  });
});
<commit_msg>Stop using JSON_REGISTRATION_IDS constant in test<commit_after>"use strict";

var chai = require('chai'),
    expect = chai.expect,
    sinon = require('sinon'),
    proxyquire = require('proxyquire'),
    senderPath = '../../lib/sender',
    Constants = require('../../lib/constants'),
    Message = require('../../lib/message');

describe('UNIT Sender', function () {
  // Use object to set arguments passed into callback
  var args = {};
  var requestStub = function (options, callback) {
    args.options = options;
    return callback( args.err, args.res, args.resBody );
  };
  var Sender = proxyquire(senderPath, { 'request': requestStub });

  describe('constructor', function () {
    var Sender = require(senderPath);

    it('should call new on constructor if user does not', function () {
      var sender = Sender();
      expect(sender).to.not.be.undefined;
      expect(sender).to.be.instanceOf(Sender);
    });

    it('should create a Sender with key and options passed in', function () {
      var options = {
        proxy: 'http://myproxy.com',
        maxSockets: 100,
        timeout: 100
      };
      var key = 'myAPIKey',
          sender = new Sender(key, options);
      expect(sender).to.be.instanceOf(Sender);
      expect(sender.key).to.equal(key);
      expect(sender.options).to.deep.equal(options);
    });

    it.skip('should do something if not passed a valid key');
  });

  describe('sendNoRetry()', function () {
    // Set arguments passed in request proxy
    function setArgs(err, res, resBody) {
      args = {
        err: err,
        res: res,
        resBody: resBody
      };
    };

    it('should set proxy, maxSockets, and/or timeout of req object if passed into constructor', function () {
      var options = {
        proxy: 'http://myproxy.com',
        maxSockets: 100,
        timeout: 1000
      };
      var sender = new Sender('mykey', options);
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', function () {});
      expect(args.options.proxy).to.equal(options.proxy);
      expect(args.options.maxSockets).to.equal(options.maxSockets);
      expect(args.options.timeout).to.equal(options.timeout);
    });

    it('should set the API key of req object if passed in API key', function () {
      var sender = new Sender('myKey');
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', function () {});
      expect(args.options.headers.Authorization).to.equal('key=myKey');
    });

    it('should stringify body of req before it is sent', function () {
      var sender = new Sender('mykey');
      var m = new Message({ collapseKey: 'Message', data: {} });
      sender.sendNoRetry(m, '', function () {});
      expect(args.options.body).to.be.a('string');
    });

    it('should set properties of body with message properties', function () {
      var mess = new Message({
        delayWhileIdle: true,
        collapseKey: 'Message',
        timeToLive: 100,
        dryRun: true,
        data: {
          name: 'Matt'
        }
      });
      var sender = new Sender('mykey');
      sender.sendNoRetry(mess, '', function () {});
      var body = JSON.parse(args.options.body);
      expect(body[Constants.PARAM_DELAY_WHILE_IDLE]).to.equal(mess.delayWhileIdle);
      expect(body[Constants.PARAM_COLLAPSE_KEY]).to.equal(mess.collapseKey);
      expect(body[Constants.PARAM_TIME_TO_LIVE]).to.equal(mess.timeToLive);
      expect(body[Constants.PARAM_DRY_RUN]).to.equal(mess.dryRun);
      expect(body[Constants.PARAM_PAYLOAD_KEY]).to.deep.equal(mess.data);
    });

    it('should set the registration ids to reg ids passed in', function () {
      var sender = new Sender('myKey');
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, ["registration id 1", "registration id 2"], function () {});
      var body = JSON.parse(args.options.body);
      expect(body.registration_ids).to.deep.equal(["registration id 1", "registration id 2"]);
    });

    it('should pass an error into callback if request returns an error', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs('an error', {}, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.calledWith('an error')).to.be.ok;
    });

    it('should pass an error into callback if response does not exist', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, undefined, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.be.a('string');
    });

    it('should return the status code as an error if returned a 500', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 500 }, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(500);
    });

    it('should return the status code as an error if returned a 401', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 401 }, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(401);
    });

    it('should return the status code as an error if returned a 400', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 400 }, {});
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(400);
    });

    it('should pass an error into the callback if resBody cannot be parsed', function () {
      var callback = sinon.spy(),
          sender = new Sender('myKey');
      setArgs(null, { statusCode: 200 }, "non-JSON string.");
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.eq('Error parsing GCM response');
    });

    it('should pass in parsed resBody into callback on success', function () {
      var callback = sinon.spy();
      var resBody = {
        message: 'woohoo!',
        success: true
      };
      var sender = new Sender('myKey');
      setArgs(null, { statusCode: 200 }, JSON.stringify(resBody));
      var m = new Message({ data: {} });
      sender.sendNoRetry(m, '', callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][1]).to.deep.equal(resBody);
    });
  });

  describe('send()', function () {
    var restore = {},
        backoff = Constants.BACKOFF_INITIAL_DELAY;
    // Set args passed into sendNoRetry
    function setArgs(err, result) {
      args = {
        err: err,
        result: result,
        tries: 0
      };
    };

    before( function () {
      restore.sendNoRetry = Sender.prototype.sendNoRetry;
      Sender.prototype.sendNoRetry = function (message, reg_ids, callback) {
        console.log('Firing send');
        args.message = message;
        args.reg_ids = reg_ids;
        args.tries++;
        var nextResult;
        if(!args.result) {
          nextResult = args.result;
        }
        else if(args.result.length > 1) {
          nextResult = args.result.slice(0,1)[0];
          args.result = args.result.slice(1,args.result.length);
        }
        else if(args.result.length == 1) {
          args.result = args.result[0];
          nextResult = args.result;
        }
        else {
          nextResult = args.result;
        }
        callback( args.err, nextResult );
      };
    });

    after( function () {
      Sender.prototype.sendNoRetry = restore.sendNoRetry;
    });

    it.skip('should do something if passed not an array for regIds');

    it('should pass an error into callback if array has no regIds', function (done) {
      var callback = function(error) {
        expect(error).to.be.a('string');
        done();
      };
      var sender = new Sender('myKey');
      sender.send({}, [], 0, callback);
    });

    it('should pass the message and the regId to sendNoRetry on call', function () {
      var sender = new Sender('myKey'),
          message = { data: {} },
          regId = [24];
      setArgs(null, {});
      sender.send(message, regId, 0, function () {});
      expect(args.message).to.equal(message);
      expect(args.reg_ids).to.equal(regId);
      expect(args.tries).to.equal(1);
    });

    it('should pass the message and the regIds to sendNoRetry on call', function () {
      var sender = new Sender('myKey'),
          message = { data: {} },
          regIds = [24, 34, 44];
      setArgs(null, {});
      sender.send(message, regIds, 0, function () {});
      expect(args.message).to.equal(message);
      expect(args.reg_ids).to.equal(regIds);
      expect(args.tries).to.equal(1);
    });

    it('should pass the result into callback if successful for id', function () {
      var callback = sinon.spy(),
          result = { success: true },
          sender = new Sender('myKey');
      setArgs(null, result);
      sender.send({}, [1], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][1]).to.equal(result);
      expect(args.tries).to.equal(1);
    });

    it('should pass the result into callback if successful for ids', function () {
      var callback = sinon.spy(),
          result = { success: true },
          sender = new Sender('myKey');
      setArgs(null, result);
      sender.send({}, [1, 2, 3], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][1]).to.equal(result);
      expect(args.tries).to.equal(1);
    });

    it('should pass the error into callback if failure and no retry for id', function () {
      var callback = sinon.spy(),
          error = 'my error',
          sender = new Sender('myKey');
      setArgs(error);
      sender.send({}, [1], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(error);
      expect(args.tries).to.equal(1);
    });

    it('should pass the error into callback if failure and no retry for ids', function () {
      var callback = sinon.spy(),
          error = 'my error',
          sender = new Sender('myKey');
      setArgs(error);
      sender.send({}, [1, 2, 3], 0, callback);
      expect(callback.calledOnce).to.be.ok;
      expect(callback.args[0][0]).to.equal(error);
      expect(args.tries).to.equal(1);
    });

    it('should retry number of times passed into call and do exponential backoff', function (done) {
      var start = new Date();
      var callback = function () {
        expect(args.tries).to.equal(2);
        expect(new Date() - start).to.be.gte(Math.pow(2, 0) * backoff);
        done();
      };
      var sender = new Sender('myKey');
      setArgs('my error');
      sender.send({ data: {}}, [1], 1, callback);
    });

    it('should retry if not all regIds were successfully sent', function (done) {
      var callback = function () {
        expect(args.tries).to.equal(3);
        // Last call of sendNoRetry should be for only failed regIds
        expect(args.reg_ids.length).to.equal(1);
        expect(args.reg_ids[0]).to.equal(3);
        done();
      };
      var sender = new Sender('myKey');
      setArgs(null, [{ results: [{}, { error: 'Unavailable' }, { error: 'Unavailable' }]}, { results: [ {}, { error: 'Unavailable' } ] }, { results: [ {} ] } ]);
      sender.send({ data: {}}, [1,2,3], {
        retries: 5,
        backoff: 100
      }, callback);
    });

    it('should retry all regIds in event of an error', function (done) {
      var start = new Date();
      var callback = function () {
        expect(args.tries).to.equal(2);
        expect(args.reg_ids.length).to.equal(3);
        done();
      };
      var sender = new Sender('myKey');
      setArgs('my error');
      sender.send({ data: {}}, [1,2,3], 1, callback);
    });

    it('should update the failures and successes correctly when retrying', function (done) {
      var callback = function(error, result) {
        expect(error).to.equal(null);
        expect(result.canonical_ids).to.equal(1);
        expect(result.success).to.equal(2);
        expect(result.failure).to.equal(0);
        done();
      };
      var sender = new Sender('myKey');
      setArgs(null, [
        { success: 1, failure: 2, canonical_ids: 0, results: [ {}, { error: 'Unavailable' }, { error: 'Unavailable' } ] },
        { success: 1, canonical_ids: 1, failure: 0, results: [ {}, {} ] }
      ]);
      sender.send({ data: {}}, [1,2,3], 3, callback);
    });

    it('should update the failures and successes correctly when retrying and failing some', function (done) {
      var callback = function(error, result) {
        expect(error).to.equal(null);
        expect(result.canonical_ids).to.equal(0);
        expect(result.success).to.equal(1);
        expect(result.failure).to.equal(2);
        done();
      };
      var sender = new Sender('myKey');
      setArgs(null, [
        { success: 0, failure: 3, canonical_ids: 0, results: [ { error: 'Unavailable' }, { error: 'Unavailable' }, { error: 'Unavailable' } ] },
        { success: 1, canonical_ids: 0, failure: 2, results: [ { error: 'Unavailable' }, { error: 'Unavailable' }, {} ] },
        { success: 0, canonical_ids: 0, failure: 2, results: [ { error: 'Unavailable' }, { error: 'Unavailable' } ] }
      ]);
      sender.send({ data: {}}, [1,2,3], {
        retries: 3,
        backoff: 100
      }, callback);
    });
  });
});
<|endoftext|>


<commit_before><commit_msg>HOTTTT<commit_after><|endoftext|>


<commit_before>'use strict'

const test = require('ava')

const { day } = require('../../src/utils/times')
const isExpired = require('../../src/utils/isExpired')

test('return false when `timestamp` is valid', (t) => {

	const date = new Date()
	const timestamp = date.getTime()

	const result = isExpired(timestamp, day)

	t.false(result)

})

test('return true when `timestamp` is invalid', (t) => {

	const date = new Date()

	date.setDate(date.getDate() - 1)

	const timestamp = date.getTime()

	const result = isExpired(timestamp, day)

	t.true(result)

})

test('return false when `timestamp` is newer than `ttl`', (t) => {

	const date = new Date()
	const timestamp = date.getTime()

	const result = isExpired(timestamp, 86400000)

	t.false(result)

})

test('return true when `timestamp` is older than `ttl`', (t) => {

	const date = new Date()

	date.setDate(date.getDate() - 1)

	const timestamp = date.getTime()

	const result = isExpired(timestamp, 0)

	t.true(result)

})<commit_msg>Adjust expired test<commit_after>'use strict'

const test = require('ava')

const { day } = require('../../src/utils/times')
const isExpired = require('../../src/utils/isExpired')

test('return true when `timestamp` has expired', (t) => {

	const date = new Date()
	date.setDate(date.getDate() - 2)
	const timestamp = date.getTime()

	const result = isExpired(timestamp, day)

	t.true(result)

})

test('return false when `timestamp` is valid', (t) => {

	const timestamp = Date.now()

	const result = isExpired(timestamp, day)

	t.false(result)

})<|endoftext|>


<commit_before>const ADT = require('../../pipeline/adt');
const DBC = require('../../pipeline/dbc');
const THREE = require('three');
const WDT = require('../../pipeline/wdt');
const WMO = require('../../pipeline/wmo');

module.exports = class Map extends THREE.Group {

  static ZEROPOINT = ADT.TILE_SIZE * ADT.GRID_SIZE / 2;

  constructor(data, wdt) {
    super();

    this.data = data;
    this.wdt = wdt;

    this.mapID = this.data.id;

    // TODO: Track ADTs in some sort of fashion
    this.wmos = {};
    this.m2s = {};
  }

  get internalName() {
    return this.data.internalName;
  }

  render(x, y) {
    // TODO: Load ADTs in a radius
    // TODO: Unloading unused ADTs outside of radius
    ADT.loadAtCoords(this.internalName, x, y).then((adt) => {
      this.add(adt);
      this.renderWMOs(adt.wmos);
      // TODO: Load M2s
    });
  }

  renderWMOs(entries) {
    entries.forEach((entry) => {
      if (!this.wmos[entry.id]) {
        this.wmos[entry.id] = WMO.load(entry.filename).then((wmo) => {
          wmo.position.set(
            -(entry.position.z - this.constructor.ZEROPOINT),
            -(entry.position.x - this.constructor.ZEROPOINT),
            entry.position.y
          );

          // Provided as (X, Z, -Y)
          wmo.rotation.set(
            entry.rotation.x * Math.PI / 180,
            -entry.rotation.z * Math.PI / 180,
            entry.rotation.y * Math.PI / 180
          );

          this.add(wmo);
        });
      }
    });
  }

  static load(id) {
    return new Promise((resolve, reject) => {
      DBC.load('Map', id).then((data) => {
        const { internalName: name } = data;
        WDT.load(`World\\Maps\\${name}\\${name}.wdt`).then((wdt) => {
          resolve(new this(data, wdt));
        });
      });
    });
  }

};
<commit_msg>Render doodads on map<commit_after>const ADT = require('../../pipeline/adt');
const DBC = require('../../pipeline/dbc');
const M2 = require('../../pipeline/m2');
const THREE = require('three');
const WDT = require('../../pipeline/wdt');
const WMO = require('../../pipeline/wmo');

module.exports = class Map extends THREE.Group {

  static ZEROPOINT = ADT.TILE_SIZE * ADT.GRID_SIZE / 2;

  constructor(data, wdt) {
    super();

    this.data = data;
    this.wdt = wdt;

    this.mapID = this.data.id;

    // TODO: Track ADTs in some sort of fashion
    this.wmos = {};
    this.doodads = {};
  }

  get internalName() {
    return this.data.internalName;
  }

  render(x, y) {
    // TODO: Load ADTs in a radius
    // TODO: Unloading unused ADTs outside of radius
    ADT.loadAtCoords(this.internalName, x, y).then((adt) => {
      this.add(adt);
      this.renderWMOs(adt.wmos);
      this.renderDoodads(adt.doodads);
    });
  }

  renderWMOs(entries) {
    entries.forEach((entry) => {
      if (!this.wmos[entry.id]) {
        this.wmos[entry.id] = WMO.load(entry.filename).then((wmo) => {
          wmo.position.set(
            -(entry.position.z - this.constructor.ZEROPOINT),
            -(entry.position.x - this.constructor.ZEROPOINT),
            entry.position.y
          );

          // Provided as (X, Z, -Y)
          wmo.rotation.set(
            entry.rotation.x * Math.PI / 180,
            -entry.rotation.z * Math.PI / 180,
            entry.rotation.y * Math.PI / 180
          );

          this.add(wmo);
        });
      }
    });
  }

  renderDoodads(entries) {
    entries.forEach((entry) => {
      if (!this.doodads[entry.id]) {
        this.doodads[entry.id] = M2.load(entry.filename).then((m2) => {
          m2.position.set(
            -(entry.position.z - this.constructor.ZEROPOINT),
            -(entry.position.x - this.constructor.ZEROPOINT),
            entry.position.y
          );

          m2.rotation.set(
            entry.rotation.x * Math.PI / 180,
            -entry.rotation.z * Math.PI / 180,
            entry.rotation.y * Math.PI / 180
          );

          if (entry.scale !== 1024) {
            const scale = entry.scale / 1024;
            m2.scale.set(scale, scale, scale);
          }

          this.add(m2);
        });
      }
    });
  }

  static load(id) {
    return new Promise((resolve, reject) => {
      DBC.load('Map', id).then((data) => {
        const { internalName: name } = data;
        WDT.load(`World\\Maps\\${name}\\${name}.wdt`).then((wdt) => {
          resolve(new this(data, wdt));
        });
      });
    });
  }

};
<|endoftext|>