<commit_before>// @flow
import queryString from "query-string";
import type { Config } from "../Domain/Config";
import type { EventList } from "../Domain/Event";
import type { Trigger, TriggerList, TriggerState } from "../Domain/Trigger";
import type { Settings } from "../Domain/Settings";
import type { TagStat } from "../Domain/Tag";
import type { PatternList } from "../Domain/Pattern";
import type { NotificationList } from "../Domain/Notification";
import type { Contact, ContactList } from "../Domain/Contact";
import type { ContactCreateInfo } from "../Domain/ContactCreateInfo";
import type { Subscription } from "../Domain/Subscription";
import type { Schedule } from "../Domain/Schedule";
import type { NotifierState } from "../Domain/MoiraServiceStates";

export type SubscriptionCreateInfo = {|
    sched: Schedule,
    tags: Array<string>,
    throttling: boolean,
    contacts: Array<string>,
    enabled: boolean,
    user: string,
    id?: string,
    ignore_recoverings: boolean,
    ignore_warnings: boolean,
    plotting?: {
        enabled: boolean,
        theme: "light" | "dark",
    },
|};

export type TagList = {|
    list: Array<string>,
|};

export type TagStatList = {|
    list: Array<TagStat>,
|};

export interface IMoiraApi {
    getSettings(): Promise<Settings>;
    getConfig(): Promise<Config>;
    getContactList(): Promise<ContactList>;
    addContact(contact: ContactCreateInfo): Promise<Contact>;
    updateContact(contact: Contact): Promise<Contact>;
    testContact(contactId: string): Promise<void>;
    addSubscription(subscription: SubscriptionCreateInfo): Promise<Subscription>;
    updateSubscription(subscription: Subscription): Promise<Subscription>;
    delSubscription(subscriptionId: string): Promise<void>;
    testSubscription(subscriptionId: string): Promise<void>;
    deleteContact(contactId: string): Promise<void>;
    getPatternList(): Promise<PatternList>;
    delPattern(pattern: string): Promise<void>;
    getTagList(): Promise<TagList>;
    getTagStats(): Promise<TagStatList>;
    delTag(tag: string): Promise<void>;
    getTriggerList(page: number, onlyProblems: boolean, tags: Array<string>): Promise<TriggerList>;
    getTrigger(id: string): Promise<Trigger>;
    addTrigger(data: $Shape<Trigger>): Promise<{ [key: string]: string }>;
    setTrigger(id: string, data: $Shape<Trigger>): Promise<{ [key: string]: string }>;
    delTrigger(id: string): Promise<void>;
    setMaintenance(
        triggerId: string,
        data: { trigger?: number, metrics?: { [metric: string]: number } }
    ): Promise<void>;
    getTriggerState(id: string): Promise<TriggerState>;
    getTriggerEvents(id: string, page: number): Promise<EventList>;
    delThrottling(triggerId: string): Promise<void>;
    delMetric(triggerId: string, metric: string): Promise<void>;
    delNoDataMetric(triggerId: string): Promise<void>;
    getNotificationList(): Promise<NotificationList>;
    delNotification(id: string): Promise<void>;
    delAllNotifications(): Promise<void>;
    delAllNotificationEvents(): Promise<void>;
    getNotifierState(): Promise<NotifierState>;
    setNotifierState(status: NotifierState): Promise<NotifierState>;
}

class ApiError extends Error {
    status: number;

    constructor({ message, status }) {
        super(message);
        this.name = "ApiError";
        this.status = status;
    }
}

const statusCode = {
    NOT_FOUND: 404,
};

export { statusCode };

export default class MoiraApi implements IMoiraApi {
    apiUrl: string;

    config: Config;

    triggerListPageSize: number = 20;

    eventHistoryPageSize: number = 100;

    constructor(apiUrl: string) {
        this.apiUrl = apiUrl;
    }

    static async checkStatus(response: Response) {
        if (!(response.status >= 200 && response.status < 300)) {
            let serverResponse: { status: string, error: string } | null = null;
            const errorText = await response.text();
            try {
                serverResponse = JSON.parse(errorText);
            } catch (error) {
                throw new ApiError({ message: errorText, status: response.status });
            }
            throw new ApiError({
                message:
                    serverResponse != null
                        ? `${serverResponse.status}${
                              serverResponse.error != null ? ` : ${serverResponse.error}` : ""
                          }`
                        : errorText,
                status: response.status,
            });
        }
    }

    async get<T>(url: string): Promise<T> {
        const fullUrl = this.apiUrl + url;
        const response = await fetch(fullUrl, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    getConfig(): Promise<Config> {
        return this.get<Config>("/config");
    }

    async getSettings(): Promise<Settings> {
        const result = await this.get<Settings>("/user/settings");
        return result;
    }

    async getContactList(): Promise<ContactList> {
        const url = `${this.apiUrl}/contact`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async addContact(contact: ContactCreateInfo): Promise<Contact> {
        const url = `${this.apiUrl}/contact`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(contact),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async testContact(contactId: string) {
        const url = `${this.apiUrl}/contact/${encodeURI(contactId)}/test`;
        const response = await fetch(url, {
            method: "POST",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async updateContact(contact: Contact): Promise<Contact> {
        const url = `${this.apiUrl}/contact/${encodeURI(contact.id)}`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(contact),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async addSubscription(subscription: SubscriptionCreateInfo): Promise<Subscription> {
        const url = `${this.apiUrl}/subscription`;
        if (subscription.id != null) {
            throw new Error("InvalidProgramState: id of subscription must be null or undefined");
        }
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(subscription),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async updateSubscription(subscription: Subscription): Promise<Subscription> {
        const url = `${this.apiUrl}/subscription/${encodeURI(subscription.id)}`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(subscription),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async testSubscription(subscriptionId: string) {
        const url = `${this.apiUrl}/subscription/${encodeURI(subscriptionId)}/test`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async deleteContact(contactId: string) {
        const url = `${this.apiUrl}/contact/${encodeURI(contactId)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getPatternList(): Promise<PatternList> {
        const url = `${this.apiUrl}/pattern`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delPattern(pattern: string) {
        const url = `${this.apiUrl}/pattern/${encodeURI(pattern)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getTagList(): Promise<TagList> {
        const url = `${this.apiUrl}/tag`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async getTagStats(): Promise<TagStatList> {
        const url = `${this.apiUrl}/tag/stats`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delTag(tag: string) {
        const url = `${this.apiUrl}/tag/${encodeURI(tag)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getTriggerList(
        page: number,
        onlyProblems: boolean,
        tags: Array<string>
    ): Promise<TriggerList> {
        const url = `${this.apiUrl}/trigger/page?${queryString.stringify(
            {
                /* eslint-disable */
                p: page,
                /* eslint-enable */
                size: this.triggerListPageSize,
                tags,
                onlyProblems,
            },
            { arrayFormat: "index", encode: true }
        )}`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async getTrigger(id: string): Promise<Trigger> {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async addTrigger(data: $Shape<Trigger>): Promise<{ [key: string]: string }> {
        const url = `${this.apiUrl}/trigger`;
        const response = await fetch(url, {
            method: "PUT",
            body: JSON.stringify(data),
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async setTrigger(id: string, data: $Shape<Trigger>): Promise<{ [key: string]: string }> {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "PUT",
            body: JSON.stringify(data),
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delTrigger(id: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async setMaintenance(
        triggerId: string,
        data: { trigger?: number, metrics?: { [metric: string]: number } }
    ) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/setMaintenance`;
        const response = await fetch(url, {
            method: "PUT",
            body: JSON.stringify(data),
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getTriggerState(id: string): Promise<TriggerState> {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}/state`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async getTriggerEvents(id: string, page: number): Promise<EventList> {
        const url = `${this.apiUrl}/event/${encodeURI(id)}?p=${page}&size=${
            this.eventHistoryPageSize
        }`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delThrottling(triggerId: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/throttling`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delMetric(triggerId: string, metric: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/metrics?name=${encodeURI(
            metric
        )}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delNoDataMetric(triggerId: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/metrics/nodata`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getNotificationList(): Promise<NotificationList> {
        const url = `${this.apiUrl}/notification?start=0&end=-1`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delNotification(id: string) {
        const url = `${this.apiUrl}/notification?id=${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delAllNotifications() {
        const url = `${this.apiUrl}/notification/all`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delAllNotificationEvents() {
        const url = `${this.apiUrl}/event/all`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delSubscription(subscriptionId: string) {
        const url = `${this.apiUrl}/subscription/${encodeURI(subscriptionId)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getNotifierState(): Promise<NotifierState> {
        const url = `${this.apiUrl}/health/notifier`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async setNotifierState(status: NotifierState): Promise<NotifierState> {
        const url = `${this.apiUrl}/health/notifier`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(status),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }
}
<commit_msg>Review fix<commit_after>// @flow
import queryString from "query-string";
import type { Config } from "../Domain/Config";
import type { EventList } from "../Domain/Event";
import type { Trigger, TriggerList, TriggerState } from "../Domain/Trigger";
import type { Settings } from "../Domain/Settings";
import type { TagStat } from "../Domain/Tag";
import type { PatternList } from "../Domain/Pattern";
import type { NotificationList } from "../Domain/Notification";
import type { Contact, ContactList } from "../Domain/Contact";
import type { ContactCreateInfo } from "../Domain/ContactCreateInfo";
import type { Subscription } from "../Domain/Subscription";
import type { Schedule } from "../Domain/Schedule";
import type { NotifierState } from "../Domain/MoiraServiceStates";

export type SubscriptionCreateInfo = {|
    sched: Schedule,
    tags: Array<string>,
    throttling: boolean,
    contacts: Array<string>,
    enabled: boolean,
    user: string,
    id?: string,
    ignore_recoverings: boolean,
    ignore_warnings: boolean,
    plotting?: {
        enabled: boolean,
        theme: "light" | "dark",
    },
|};

export type TagList = {|
    list: Array<string>,
|};

export type TagStatList = {|
    list: Array<TagStat>,
|};

export interface IMoiraApi {
    getSettings(): Promise<Settings>;
    getConfig(): Promise<Config>;
    getContactList(): Promise<ContactList>;
    addContact(contact: ContactCreateInfo): Promise<Contact>;
    updateContact(contact: Contact): Promise<Contact>;
    testContact(contactId: string): Promise<void>;
    addSubscription(subscription: SubscriptionCreateInfo): Promise<Subscription>;
    updateSubscription(subscription: Subscription): Promise<Subscription>;
    delSubscription(subscriptionId: string): Promise<void>;
    testSubscription(subscriptionId: string): Promise<void>;
    deleteContact(contactId: string): Promise<void>;
    getPatternList(): Promise<PatternList>;
    delPattern(pattern: string): Promise<void>;
    getTagList(): Promise<TagList>;
    getTagStats(): Promise<TagStatList>;
    delTag(tag: string): Promise<void>;
    getTriggerList(page: number, onlyProblems: boolean, tags: Array<string>): Promise<TriggerList>;
    getTrigger(id: string): Promise<Trigger>;
    addTrigger(data: $Shape<Trigger>): Promise<{ [key: string]: string }>;
    setTrigger(id: string, data: $Shape<Trigger>): Promise<{ [key: string]: string }>;
    delTrigger(id: string): Promise<void>;
    setMaintenance(
        triggerId: string,
        data: { trigger?: number, metrics?: { [metric: string]: number } }
    ): Promise<void>;
    getTriggerState(id: string): Promise<TriggerState>;
    getTriggerEvents(id: string, page: number): Promise<EventList>;
    delThrottling(triggerId: string): Promise<void>;
    delMetric(triggerId: string, metric: string): Promise<void>;
    delNoDataMetric(triggerId: string): Promise<void>;
    getNotificationList(): Promise<NotificationList>;
    delNotification(id: string): Promise<void>;
    delAllNotifications(): Promise<void>;
    delAllNotificationEvents(): Promise<void>;
    getNotifierState(): Promise<NotifierState>;
    setNotifierState(status: NotifierState): Promise<NotifierState>;
}

class ApiError extends Error {
    status: number;

    constructor({ message, status }) {
        super(message);
        this.name = "ApiError";
        this.status = status;
    }
}

const statusCode = {
    NOT_FOUND: 404,
};

export { statusCode };

export default class MoiraApi implements IMoiraApi {
    apiUrl: string;

    config: Config;

    triggerListPageSize: number = 20;

    eventHistoryPageSize: number = 100;

    constructor(apiUrl: string) {
        this.apiUrl = apiUrl;
    }

    static async checkStatus(response: Response) {
        if (!(response.status >= 200 && response.status < 300)) {
            let serverResponse: { status: string, error: string } | null = null;
            const errorText = await response.text();
            try {
                serverResponse = JSON.parse(errorText);
            } catch (error) {
                throw new ApiError({ message: errorText, status: response.status });
            }
            throw new ApiError({
                message: serverResponse
                    ? serverResponse.status +
                      (serverResponse.error ? `: ${serverResponse.error}` : "")
                    : errorText,
                status: response.status,
            });
        }
    }

    async get<T>(url: string): Promise<T> {
        const fullUrl = this.apiUrl + url;
        const response = await fetch(fullUrl, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    getConfig(): Promise<Config> {
        return this.get<Config>("/config");
    }

    async getSettings(): Promise<Settings> {
        const result = await this.get<Settings>("/user/settings");
        return result;
    }

    async getContactList(): Promise<ContactList> {
        const url = `${this.apiUrl}/contact`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async addContact(contact: ContactCreateInfo): Promise<Contact> {
        const url = `${this.apiUrl}/contact`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(contact),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async testContact(contactId: string) {
        const url = `${this.apiUrl}/contact/${encodeURI(contactId)}/test`;
        const response = await fetch(url, {
            method: "POST",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async updateContact(contact: Contact): Promise<Contact> {
        const url = `${this.apiUrl}/contact/${encodeURI(contact.id)}`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(contact),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async addSubscription(subscription: SubscriptionCreateInfo): Promise<Subscription> {
        const url = `${this.apiUrl}/subscription`;
        if (subscription.id != null) {
            throw new Error("InvalidProgramState: id of subscription must be null or undefined");
        }
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(subscription),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async updateSubscription(subscription: Subscription): Promise<Subscription> {
        const url = `${this.apiUrl}/subscription/${encodeURI(subscription.id)}`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(subscription),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async testSubscription(subscriptionId: string) {
        const url = `${this.apiUrl}/subscription/${encodeURI(subscriptionId)}/test`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async deleteContact(contactId: string) {
        const url = `${this.apiUrl}/contact/${encodeURI(contactId)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getPatternList(): Promise<PatternList> {
        const url = `${this.apiUrl}/pattern`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delPattern(pattern: string) {
        const url = `${this.apiUrl}/pattern/${encodeURI(pattern)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getTagList(): Promise<TagList> {
        const url = `${this.apiUrl}/tag`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async getTagStats(): Promise<TagStatList> {
        const url = `${this.apiUrl}/tag/stats`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delTag(tag: string) {
        const url = `${this.apiUrl}/tag/${encodeURI(tag)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getTriggerList(
        page: number,
        onlyProblems: boolean,
        tags: Array<string>
    ): Promise<TriggerList> {
        const url = `${this.apiUrl}/trigger/page?${queryString.stringify(
            {
                /* eslint-disable */
                p: page,
                /* eslint-enable */
                size: this.triggerListPageSize,
                tags,
                onlyProblems,
            },
            { arrayFormat: "index", encode: true }
        )}`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async getTrigger(id: string): Promise<Trigger> {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async addTrigger(data: $Shape<Trigger>): Promise<{ [key: string]: string }> {
        const url = `${this.apiUrl}/trigger`;
        const response = await fetch(url, {
            method: "PUT",
            body: JSON.stringify(data),
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async setTrigger(id: string, data: $Shape<Trigger>): Promise<{ [key: string]: string }> {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "PUT",
            body: JSON.stringify(data),
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delTrigger(id: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async setMaintenance(
        triggerId: string,
        data: { trigger?: number, metrics?: { [metric: string]: number } }
    ) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/setMaintenance`;
        const response = await fetch(url, {
            method: "PUT",
            body: JSON.stringify(data),
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getTriggerState(id: string): Promise<TriggerState> {
        const url = `${this.apiUrl}/trigger/${encodeURI(id)}/state`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async getTriggerEvents(id: string, page: number): Promise<EventList> {
        const url = `${this.apiUrl}/event/${encodeURI(id)}?p=${page}&size=${
            this.eventHistoryPageSize
        }`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delThrottling(triggerId: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/throttling`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delMetric(triggerId: string, metric: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/metrics?name=${encodeURI(
            metric
        )}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delNoDataMetric(triggerId: string) {
        const url = `${this.apiUrl}/trigger/${encodeURI(triggerId)}/metrics/nodata`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getNotificationList(): Promise<NotificationList> {
        const url = `${this.apiUrl}/notification?start=0&end=-1`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async delNotification(id: string) {
        const url = `${this.apiUrl}/notification?id=${encodeURI(id)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delAllNotifications() {
        const url = `${this.apiUrl}/notification/all`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delAllNotificationEvents() {
        const url = `${this.apiUrl}/event/all`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async delSubscription(subscriptionId: string) {
        const url = `${this.apiUrl}/subscription/${encodeURI(subscriptionId)}`;
        const response = await fetch(url, {
            method: "DELETE",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
    }

    async getNotifierState(): Promise<NotifierState> {
        const url = `${this.apiUrl}/health/notifier`;
        const response = await fetch(url, {
            method: "GET",
            credentials: "same-origin",
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }

    async setNotifierState(status: NotifierState): Promise<NotifierState> {
        const url = `${this.apiUrl}/health/notifier`;
        const response = await fetch(url, {
            method: "PUT",
            credentials: "same-origin",
            body: JSON.stringify(status),
        });
        await MoiraApi.checkStatus(response);
        return response.json();
    }
}
<|endoftext|>


<commit_before>import reboot from 'electron-squirrel-startup';
if (reboot) {
  process.exit(0);
}

import raven from 'raven';
import request from 'request';
import path from 'path';
import electron from 'electron';
import * as packageJSON from './package.json';
import LocalStorage from './common/LocalStorage';
import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer';

// Some useful helpers
const IS_DEV = process.env.INSOMNIA_ENV === 'development';
const IS_MAC = process.platform === 'darwin';
const IS_WINDOWS = process.platform === 'win32';
const IS_LINUX = !IS_MAC && !IS_WINDOWS;

const ravenClient = new raven.Client('https://786e43ae199c4757a9ea4a48a9abd17d@sentry.io/109702', {
  environment: process.env.INSOMNIA_ENV || 'production',
  release: packageJSON.version,
  logger: 'electron.main'
});

if (!IS_DEV) {
  ravenClient.patchGlobal();
}

const {app, dialog, shell, ipcMain, autoUpdater, Menu, BrowserWindow, webContents} = electron;
const {version: appVersion, productName: appName} = packageJSON;

const UPDATE_URLS = {
  // Add `r` param to help cache bust
  darwin: `https://updates.insomnia.rest/builds/check/mac?v=${appVersion}`,
  linux: `https://updates.insomnia.rest/builds/check/linux?v=${appVersion}`,
  win32: `https://downloads.insomnia.rest/win`
};

const DOWNLOAD_URL = 'http://download.insomnia.rest';

const localStoragePath = path.join(app.getPath('userData'), 'localStorage');
const localStorage = new LocalStorage(localStoragePath);

let mainWindow = null;
let hasPromptedForUpdates = false;

// Enable this for CSS grid layout :)
app.commandLine.appendSwitch('enable-experimental-web-platform-features');

process.on('uncaughtException', e => {
  if (IS_DEV) {
    console.error(e);
  } else {
    ravenClient.captureError(e, {});
  }
});

autoUpdater.on('error', e => {
  if (IS_DEV) {
    console.error(e);
  } else {
    // Don't report autoUpdater error. They are way too noisy
  }
});

autoUpdater.on('update-not-available', () => {
  console.log('-- Update Not Available --')
});

autoUpdater.on('update-available', () => {
  console.log('-- Update Available --');
});

autoUpdater.on('update-downloaded', (e, releaseNotes, releaseName, releaseDate, updateUrl) => {
  console.log(`-- Update Downloaded ${releaseName} --`);
  showUpdateNotification();
});

function checkForUpdates () {
  if (hasPromptedForUpdates) {
    // We've already prompted for updates. Don't bug the user anymore
    return;
  }

  if (IS_DEV) {
    console.log('-- Skipping update check in Development --');
    return;
  }

  if (IS_LINUX) {
    try {
      request.get(UPDATE_URLS.linux, null, (err, response) => {
        if (err) {
          ravenClient.captureError(err);
          return;
        }

        if (response.statusCode === 200) {
          showDownloadModal(response.body);
        } else {
          // No update available (should be STATUS=204)
        }
      });
    } catch (e) {
      ravenClient.captureException(e);
    }
  } else {
    try {
      autoUpdater.setFeedURL(UPDATE_URLS[process.platform]);
      autoUpdater.checkForUpdates();
    } catch (e) {
      // This will fail in development
    }
  }
}

function showUnresponsiveModal () {
  dialog.showMessageBox({
    type: 'info',
    buttons: ['Cancel', 'Reload'],
    defaultId: 1,
    cancelId: 0,
    title: 'Unresponsive',
    message: 'Insomnia has become unresponsive. Do you want to reload?'
  }, id => {
    if (id === 1) {
      mainWindow.destroy();
      createWindow();
    }
  });
}

function showUpdateNotification () {
  if (hasPromptedForUpdates) {
    return;
  }

  const window = BrowserWindow.getFocusedWindow();
  if (!window || !window.webContents) {
    return;
  }

  window.webContents.send('update-available');
  hasPromptedForUpdates = true;
}

function trackEvent (...args) {
  const window = BrowserWindow.getFocusedWindow();
  if (!window || !window.webContents) {
    return;
  }

  window.webContents.send('analytics-track-event', args);
}

function showDownloadModal (version) {
  hasPromptedForUpdates = true;

  dialog.showMessageBox({
    type: 'info',
    buttons: ['Download', 'Later'],
    defaultId: 0,
    cancelId: 1,
    title: 'Insomnia Update Available',
    message: `Exciting news!\n\nVersion ${version} of Insomnia is now available.\n\n\n~Gregory`
  }, id => {
    if (id === 0) {
      console.log('-- Installing Update --');
      shell.openExternal(DOWNLOAD_URL);
    } else {
      console.log('-- Cancel Update --');
    }
  });
}

ipcMain.on('check-for-updates', () => {
  console.log('-- Checking for Updates --');
  checkForUpdates();
});

function saveBounds () {
  if (!mainWindow) {
    return;
  }

  const fullscreen = mainWindow.isFullScreen();

  // Only save the size if we're not in fullscreen
  if (!fullscreen) {
    localStorage.setItem('bounds', mainWindow.getBounds());
    localStorage.setItem('fullscreen', false);
  } else {
    localStorage.setItem('fullscreen', true);
  }
}

function getBounds () {
  let bounds = {};
  let fullscreen = false;
  try {
    bounds = localStorage.getItem('bounds', {});
    fullscreen = localStorage.getItem('fullscreen', false);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse window bounds', e);
  }

  return {bounds, fullscreen};
}

function saveZoomFactor (zoomFactor) {
  localStorage.setItem('zoomFactor', zoomFactor);
}

function getZoomFactor () {
  let zoomFactor = 1;
  try {
    zoomFactor = localStorage.getItem('zoomFactor', 1);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse zoomFactor', e);
  }

  return zoomFactor;
}

// Quit when all windows are closed (except on Mac).
app.on('window-all-closed', () => {
  if (!IS_MAC) {
    app.quit();
  }
});

// Mac-only, when the user clicks the doc icon
app.on('activate', (e, hasVisibleWindows) => {
  // Create a new window when clicking the doc icon if there isn't one open
  if (!hasVisibleWindows) {
    try {
      createWindow()
    } catch (e) {
      // This might happen if 'ready' hasn't fired yet. So we're just going
      // to silence these errors.
      console.log('-- App not ready to "activate" yet --');
    }
  }
});

// When the app is first launched
app.on('ready', () => {
  createWindow();
  checkForUpdates();
});

function createWindow () {
  const zoomFactor = getZoomFactor();
  const {bounds, fullscreen} = getBounds();
  const {x, y, width, height} = bounds;

  // Make sure we don't place the window outside of the visible space
  let maxX = 0;
  let maxY = 0;
  for (const d of electron.screen.getAllDisplays()) {
    // Set the maximum placement location to 50 pixels short of the end
    maxX = Math.max(maxX, d.bounds.x + d.bounds.width - 50);
    maxY = Math.max(maxY, d.bounds.y + d.bounds.height - 50);
  }
  const finalX = Math.min(maxX, x);
  const finalY = Math.min(maxX, y);

  mainWindow = new BrowserWindow({
    // Make sure we don't initialize the window outside the bounds
    x: finalX,
    y: finalY,
    fullscreen: fullscreen,
    fullscreenable: true,
    title: appName,
    width: width || 1200,
    height: height || 600,
    minHeight: 500,
    minWidth: 500,
    acceptFirstMouse: true,
    webPreferences: {
      zoomFactor: zoomFactor
    }
  });

  let _resizeTimeout = null;
  mainWindow.on('resize', e => {
    saveBounds();

    clearTimeout(_resizeTimeout);
    _resizeTimeout = setTimeout(() => {
      trackEvent('Window', 'Resize');
    }, 1000);
  });
  mainWindow.on('move', e => {
    saveBounds();
    trackEvent('Window', 'Move');
  });
  mainWindow.on('unresponsive', e => {
    showUnresponsiveModal();
    trackEvent('Window', 'Unresponsive');
  });

  // and load the app.html of the app.
  // TODO: Use path.join for this
  mainWindow.loadURL(`file://${__dirname}/renderer.html`);

  // Emitted when the window is closed.
  mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
    trackEvent('Window', 'Close');
  });

  require('electron-context-menu')({});

  var template = [
    {
      label: "Application",
      submenu: [
        {
          label: `About ${appName}`,
          role: 'about',
          visible: IS_MAC
        },
        {
          type: "separator",
          visible: IS_MAC
        },
        {
          label: "Preferences",
          accelerator: "CmdOrCtrl+,",
          click: function (menuItem, window, e) {
            if (!window || !window.webContents) {
              return
            }

            window.webContents.send('toggle-preferences');
            trackEvent('App Menu', 'Preferences');
          }
        },
        {
          label: "Changelog",
          click: function (menuItem, window, e) {
            if (!window || !window.webContents) {
              return
            }

            window.webContents.send('toggle-changelog');
            trackEvent('App Menu', 'Changelog');
          }
        },
        {
          type: "separator",
          visible: IS_MAC
        },
        {
          role: "hide",
          visible: IS_MAC
        },
        {
          role: "hideothers",
          visible: IS_MAC
        },
        {type: "separator"},
        {
          label: "Quit",
          accelerator: "Command+Q",
          click: function () {
            app.quit();
          }
        }
      ]
    },
    {
      label: "Edit",
      submenu: [{
        label: "Undo",
        accelerator: "CmdOrCtrl+Z",
        selector: "undo:"
      }, {
        label: "Redo",
        accelerator: "Shift+CmdOrCtrl+Z",
        selector: "redo:"
      }, {
        type: "separator"
      }, {
        label: "Cut",
        accelerator: "CmdOrCtrl+X",
        selector: "cut:"
      }, {
        label: "Copy",
        accelerator: "CmdOrCtrl+C",
        selector: "copy:"
      }, {
        label: "Paste",
        accelerator: "CmdOrCtrl+V",
        selector: "paste:"
      }, {
        label: "Select All",
        accelerator: "CmdOrCtrl+A",
        selector: "selectAll:"
      }]
    },
    {
      label: "View",
      submenu: [
        {
          role: 'togglefullscreen'
        },
        {
          label: "Actual Size",
          accelerator: "CmdOrCtrl+0",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            const zoomFactor = 1;
            window.webContents.setZoomFactor(zoomFactor);
            saveZoomFactor(zoomFactor);
            trackEvent('App Menu', 'Zoom Reset');
          }
        },
        {
          label: "Zoom In",
          accelerator: IS_MAC ? "CmdOrCtrl+Plus" : "CmdOrCtrl+=",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            const zoomFactor = Math.min(1.8, getZoomFactor() + 0.05);
            window.webContents.setZoomFactor(zoomFactor);

            saveZoomFactor(zoomFactor);
            trackEvent('App Menu', 'Zoom In');
          }
        },
        {
          label: "Zoom Out",
          accelerator: "CmdOrCtrl+-",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            const zoomFactor = Math.max(0.5, getZoomFactor() - 0.05);
            window.webContents.setZoomFactor(zoomFactor);
            saveZoomFactor(zoomFactor);
            trackEvent('App Menu', 'Zoom Out');
          }
        },
        {
          label: "Toggle Sidebar",
          accelerator: "CmdOrCtrl+\\",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            window.webContents.send('toggle-sidebar');
            trackEvent('App Menu', 'Toggle Sidebar');
          }
        }
      ]
    }, {
      label: "Window",
      role: "window",
      submenu: [
        {role: 'minimize'},
        ...(IS_MAC ? [{role: 'close'}] : [])
      ]
    }, {
      label: "Help",
      role: "help",
      id: "help",
      submenu: [
        {
          label: "Contact Support",
          click: () => {
            trackEvent('App Menu', 'Contact');
            shell.openExternal('mailto:support@insomnia.rest');
          }
        },
        {
          label: "Insomnia Help",
          accelerator: "CmdOrCtrl+?",
          click: (e) => {
            trackEvent('App Menu', 'Help');
            shell.openExternal('http://docs.insomnia.rest');
          }
        }
      ]
    }
  ];

  if (IS_DEV) {
    installExtension(REACT_DEVELOPER_TOOLS)
      .then((name) => console.log(`Added Extension:  ${name}`))
      .catch((err) => console.log('An error occurred: ', err));

    template.push({
      label: 'Developer',
      position: 'before=help',
      submenu: [{
        label: 'Reload',
        accelerator: 'Command+R',
        click: function () {
          mainWindow.reload();
        }
      }, {
        label: 'Toggle DevTools',
        accelerator: 'Alt+Command+I',
        click: function () {
          mainWindow.toggleDevTools();
        }
      }]
    });
  }

  Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
<commit_msg>Fixed analytics move event<commit_after>import reboot from 'electron-squirrel-startup';
if (reboot) {
  process.exit(0);
}

import raven from 'raven';
import request from 'request';
import path from 'path';
import electron from 'electron';
import * as packageJSON from './package.json';
import LocalStorage from './common/LocalStorage';
import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer';

// Some useful helpers
const IS_DEV = process.env.INSOMNIA_ENV === 'development';
const IS_MAC = process.platform === 'darwin';
const IS_WINDOWS = process.platform === 'win32';
const IS_LINUX = !IS_MAC && !IS_WINDOWS;

const ravenClient = new raven.Client('https://786e43ae199c4757a9ea4a48a9abd17d@sentry.io/109702', {
  environment: process.env.INSOMNIA_ENV || 'production',
  release: packageJSON.version,
  logger: 'electron.main'
});

if (!IS_DEV) {
  ravenClient.patchGlobal();
}

const {app, dialog, shell, ipcMain, autoUpdater, Menu, BrowserWindow, webContents} = electron;
const {version: appVersion, productName: appName} = packageJSON;

const UPDATE_URLS = {
  // Add `r` param to help cache bust
  darwin: `https://updates.insomnia.rest/builds/check/mac?v=${appVersion}`,
  linux: `https://updates.insomnia.rest/builds/check/linux?v=${appVersion}`,
  win32: `https://downloads.insomnia.rest/win`
};

const DOWNLOAD_URL = 'http://download.insomnia.rest';

const localStoragePath = path.join(app.getPath('userData'), 'localStorage');
const localStorage = new LocalStorage(localStoragePath);

let mainWindow = null;
let hasPromptedForUpdates = false;

// Enable this for CSS grid layout :)
app.commandLine.appendSwitch('enable-experimental-web-platform-features');

process.on('uncaughtException', e => {
  if (IS_DEV) {
    console.error(e);
  } else {
    ravenClient.captureError(e, {});
  }
});

autoUpdater.on('error', e => {
  if (IS_DEV) {
    console.error(e);
  } else {
    // Don't report autoUpdater error. They are way too noisy
  }
});

autoUpdater.on('update-not-available', () => {
  console.log('-- Update Not Available --')
});

autoUpdater.on('update-available', () => {
  console.log('-- Update Available --');
});

autoUpdater.on('update-downloaded', (e, releaseNotes, releaseName, releaseDate, updateUrl) => {
  console.log(`-- Update Downloaded ${releaseName} --`);
  showUpdateNotification();
});

function checkForUpdates () {
  if (hasPromptedForUpdates) {
    // We've already prompted for updates. Don't bug the user anymore
    return;
  }

  if (IS_DEV) {
    console.log('-- Skipping update check in Development --');
    return;
  }

  if (IS_LINUX) {
    try {
      request.get(UPDATE_URLS.linux, null, (err, response) => {
        if (err) {
          ravenClient.captureError(err);
          return;
        }

        if (response.statusCode === 200) {
          showDownloadModal(response.body);
        } else {
          // No update available (should be STATUS=204)
        }
      });
    } catch (e) {
      ravenClient.captureException(e);
    }
  } else {
    try {
      autoUpdater.setFeedURL(UPDATE_URLS[process.platform]);
      autoUpdater.checkForUpdates();
    } catch (e) {
      // This will fail in development
    }
  }
}

function showUnresponsiveModal () {
  dialog.showMessageBox({
    type: 'info',
    buttons: ['Cancel', 'Reload'],
    defaultId: 1,
    cancelId: 0,
    title: 'Unresponsive',
    message: 'Insomnia has become unresponsive. Do you want to reload?'
  }, id => {
    if (id === 1) {
      mainWindow.destroy();
      createWindow();
    }
  });
}

function showUpdateNotification () {
  if (hasPromptedForUpdates) {
    return;
  }

  const window = BrowserWindow.getFocusedWindow();
  if (!window || !window.webContents) {
    return;
  }

  window.webContents.send('update-available');
  hasPromptedForUpdates = true;
}

function trackEvent (...args) {
  const window = BrowserWindow.getFocusedWindow();
  if (!window || !window.webContents) {
    return;
  }

  window.webContents.send('analytics-track-event', args);
}

function showDownloadModal (version) {
  hasPromptedForUpdates = true;

  dialog.showMessageBox({
    type: 'info',
    buttons: ['Download', 'Later'],
    defaultId: 0,
    cancelId: 1,
    title: 'Insomnia Update Available',
    message: `Exciting news!\n\nVersion ${version} of Insomnia is now available.\n\n\n~Gregory`
  }, id => {
    if (id === 0) {
      console.log('-- Installing Update --');
      shell.openExternal(DOWNLOAD_URL);
    } else {
      console.log('-- Cancel Update --');
    }
  });
}

ipcMain.on('check-for-updates', () => {
  console.log('-- Checking for Updates --');
  checkForUpdates();
});

function saveBounds () {
  if (!mainWindow) {
    return;
  }

  const fullscreen = mainWindow.isFullScreen();

  // Only save the size if we're not in fullscreen
  if (!fullscreen) {
    localStorage.setItem('bounds', mainWindow.getBounds());
    localStorage.setItem('fullscreen', false);
  } else {
    localStorage.setItem('fullscreen', true);
  }
}

function getBounds () {
  let bounds = {};
  let fullscreen = false;
  try {
    bounds = localStorage.getItem('bounds', {});
    fullscreen = localStorage.getItem('fullscreen', false);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse window bounds', e);
  }

  return {bounds, fullscreen};
}

function saveZoomFactor (zoomFactor) {
  localStorage.setItem('zoomFactor', zoomFactor);
}

function getZoomFactor () {
  let zoomFactor = 1;
  try {
    zoomFactor = localStorage.getItem('zoomFactor', 1);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse zoomFactor', e);
  }

  return zoomFactor;
}

// Quit when all windows are closed (except on Mac).
app.on('window-all-closed', () => {
  if (!IS_MAC) {
    app.quit();
  }
});

// Mac-only, when the user clicks the doc icon
app.on('activate', (e, hasVisibleWindows) => {
  // Create a new window when clicking the doc icon if there isn't one open
  if (!hasVisibleWindows) {
    try {
      createWindow()
    } catch (e) {
      // This might happen if 'ready' hasn't fired yet. So we're just going
      // to silence these errors.
      console.log('-- App not ready to "activate" yet --');
    }
  }
});

// When the app is first launched
app.on('ready', () => {
  createWindow();
  checkForUpdates();
});

function createWindow () {
  const zoomFactor = getZoomFactor();
  const {bounds, fullscreen} = getBounds();
  const {x, y, width, height} = bounds;

  // Make sure we don't place the window outside of the visible space
  let maxX = 0;
  let maxY = 0;
  for (const d of electron.screen.getAllDisplays()) {
    // Set the maximum placement location to 50 pixels short of the end
    maxX = Math.max(maxX, d.bounds.x + d.bounds.width - 50);
    maxY = Math.max(maxY, d.bounds.y + d.bounds.height - 50);
  }
  const finalX = Math.min(maxX, x);
  const finalY = Math.min(maxX, y);

  mainWindow = new BrowserWindow({
    // Make sure we don't initialize the window outside the bounds
    x: finalX,
    y: finalY,
    fullscreen: fullscreen,
    fullscreenable: true,
    title: appName,
    width: width || 1200,
    height: height || 600,
    minHeight: 500,
    minWidth: 500,
    acceptFirstMouse: true,
    webPreferences: {
      zoomFactor: zoomFactor
    }
  });

  let _resizeTimeout = null;
  mainWindow.on('resize', e => {
    saveBounds();

    clearTimeout(_resizeTimeout);
    _resizeTimeout = setTimeout(() => {
      trackEvent('Window', 'Resize');
    }, 1000);
  });

  let _moveTimeout = null;
  mainWindow.on('move', e => {
    saveBounds();

    clearTimeout(_moveTimeout);
    _moveTimeout = setTimeout(() => {
      trackEvent('Window', 'Move');
    });
  });
  mainWindow.on('unresponsive', e => {
    showUnresponsiveModal();
    trackEvent('Window', 'Unresponsive');
  });

  // and load the app.html of the app.
  // TODO: Use path.join for this
  mainWindow.loadURL(`file://${__dirname}/renderer.html`);

  // Emitted when the window is closed.
  mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
    trackEvent('Window', 'Close');
  });

  require('electron-context-menu')({});

  var template = [
    {
      label: "Application",
      submenu: [
        {
          label: `About ${appName}`,
          role: 'about',
          visible: IS_MAC
        },
        {
          type: "separator",
          visible: IS_MAC
        },
        {
          label: "Preferences",
          accelerator: "CmdOrCtrl+,",
          click: function (menuItem, window, e) {
            if (!window || !window.webContents) {
              return
            }

            window.webContents.send('toggle-preferences');
            trackEvent('App Menu', 'Preferences');
          }
        },
        {
          label: "Changelog",
          click: function (menuItem, window, e) {
            if (!window || !window.webContents) {
              return
            }

            window.webContents.send('toggle-changelog');
            trackEvent('App Menu', 'Changelog');
          }
        },
        {
          type: "separator",
          visible: IS_MAC
        },
        {
          role: "hide",
          visible: IS_MAC
        },
        {
          role: "hideothers",
          visible: IS_MAC
        },
        {type: "separator"},
        {
          label: "Quit",
          accelerator: "Command+Q",
          click: function () {
            app.quit();
          }
        }
      ]
    },
    {
      label: "Edit",
      submenu: [{
        label: "Undo",
        accelerator: "CmdOrCtrl+Z",
        selector: "undo:"
      }, {
        label: "Redo",
        accelerator: "Shift+CmdOrCtrl+Z",
        selector: "redo:"
      }, {
        type: "separator"
      }, {
        label: "Cut",
        accelerator: "CmdOrCtrl+X",
        selector: "cut:"
      }, {
        label: "Copy",
        accelerator: "CmdOrCtrl+C",
        selector: "copy:"
      }, {
        label: "Paste",
        accelerator: "CmdOrCtrl+V",
        selector: "paste:"
      }, {
        label: "Select All",
        accelerator: "CmdOrCtrl+A",
        selector: "selectAll:"
      }]
    },
    {
      label: "View",
      submenu: [
        {
          role: 'togglefullscreen'
        },
        {
          label: "Actual Size",
          accelerator: "CmdOrCtrl+0",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            const zoomFactor = 1;
            window.webContents.setZoomFactor(zoomFactor);
            saveZoomFactor(zoomFactor);
            trackEvent('App Menu', 'Zoom Reset');
          }
        },
        {
          label: "Zoom In",
          accelerator: IS_MAC ? "CmdOrCtrl+Plus" : "CmdOrCtrl+=",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            const zoomFactor = Math.min(1.8, getZoomFactor() + 0.05);
            window.webContents.setZoomFactor(zoomFactor);

            saveZoomFactor(zoomFactor);
            trackEvent('App Menu', 'Zoom In');
          }
        },
        {
          label: "Zoom Out",
          accelerator: "CmdOrCtrl+-",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            const zoomFactor = Math.max(0.5, getZoomFactor() - 0.05);
            window.webContents.setZoomFactor(zoomFactor);
            saveZoomFactor(zoomFactor);
            trackEvent('App Menu', 'Zoom Out');
          }
        },
        {
          label: "Toggle Sidebar",
          accelerator: "CmdOrCtrl+\\",
          click: () => {
            const window = BrowserWindow.getFocusedWindow();
            if (!window || !window.webContents) {
              return;
            }

            window.webContents.send('toggle-sidebar');
            trackEvent('App Menu', 'Toggle Sidebar');
          }
        }
      ]
    }, {
      label: "Window",
      role: "window",
      submenu: [
        {role: 'minimize'},
        ...(IS_MAC ? [{role: 'close'}] : [])
      ]
    }, {
      label: "Help",
      role: "help",
      id: "help",
      submenu: [
        {
          label: "Contact Support",
          click: () => {
            trackEvent('App Menu', 'Contact');
            shell.openExternal('mailto:support@insomnia.rest');
          }
        },
        {
          label: "Insomnia Help",
          accelerator: "CmdOrCtrl+?",
          click: (e) => {
            trackEvent('App Menu', 'Help');
            shell.openExternal('http://docs.insomnia.rest');
          }
        }
      ]
    }
  ];

  if (IS_DEV) {
    installExtension(REACT_DEVELOPER_TOOLS)
      .then((name) => console.log(`Added Extension:  ${name}`))
      .catch((err) => console.log('An error occurred: ', err));

    template.push({
      label: 'Developer',
      position: 'before=help',
      submenu: [{
        label: 'Reload',
        accelerator: 'Command+R',
        click: function () {
          mainWindow.reload();
        }
      }, {
        label: 'Toggle DevTools',
        accelerator: 'Alt+Command+I',
        click: function () {
          mainWindow.toggleDevTools();
        }
      }]
    });
  }

  Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
<|endoftext|>


<commit_before>Controllers.controller("LoginCtrl",
    ["$log", "$scope", "$http", "$rootScope", "$location", "$timeout", "$filter",
    "Connection", "User", "Channel", "Parser",
    function ($log, $scope, $http, $rootScope, $location, $timeout, $filter,
    Connection, User, Channel, Parser)
    {
        var mustKill = false;
        $scope.user = User.get("~");
        $scope.user.nickName = $.cookie("nickName");
        if ($.cookie("color"))
        {
            $scope.user.color = $.cookie("color");
        }

        $location.hash("");

        $http.jsonp("http://geoip.yonom.org/index.php?callback=JSON_CALLBACK")
        .success(function (data)
        {
            $scope.user.country = data.geoplugin_countryName;
            $scope.user.flag = data.geoplugin_countryCode;
        });

        $rootScope.$on("err_nicknameinuse", function (evt)
        {
            if ($scope.user.password)
            {
                Connection.send("NICK " + $scope.user.nickName + "_");

                mustKill = true;

                return;
            }

            Connection.close();
            $scope.reset();

            $rootScope.$apply(function ()
            {
                $scope.error = "Nickname is in use";
            });
        });

        $rootScope.$on("channel.joined", function (evt, channel)
        {
            $scope.connecting = false;
            $scope.connected = false;
            $scope.error = "";
        });

        $scope.reset = function ()
        {
            $scope.connecting = false;
            $scope.connected = false;
            $scope.hostToken = "";

            if (VERSION === "local")
            {
                $scope.port = 81;
            } else
            {
                $scope.port = 82;
            }
            $scope.error = "";
        }

        $scope.login = function ()
        {
            $scope.reset();
            User.register($scope.user.nickName);
            User.alias("~", $scope.user.nickName);

            $http.jsonp("http://kaslai.us/coldstorm/fixip2.php?nick=" +
                encodeURI($scope.user.nickName) + "&random=" +
                Math.floor(Math.random() * 10000000) +
		        "&callback=JSON_CALLBACK").success(function (data)
		        {
		            $scope.hostToken = data.tag;
		        });

            $.cookie("nickName", $scope.user.nickName, { expires: new Date(2017, 00, 01) });
            $.cookie("color", $scope.user.color, { expires: new Date(2017, 00, 01) });

            $scope.connect();
        };

        $scope.connect = function ()
        {
            if ($scope.connecting === false)
            {
                $scope.connecting = true;

                // Attempt to connect
                $log.log("connecting to ws://frogbox.es:" + $scope.port)
                Connection.connect("ws://frogbox.es:" + $scope.port);

                Connection.onOpen(function ()
                {
                    // Connection successfully opened
                    $scope.reset();
                    $scope.connected = true;

                    Connection.send("NICK " + $scope.user.nickName);
                    Connection.send("USER " +
                        $scope.user.color.substring(1).toUpperCase() +
                        $scope.user.flag + " - - :New coldstormer");

                    Connection.onWelcome(function ()
                    {
                        if (mustKill)
                        {
                            Connection.send("PRIVMSG NickServ :GHOST " +
                                $scope.user.nickName + " " +
                                $scope.user.password);
                        }

                        if ($scope.user.password)
                        {
                            Connection.send("PRIVMSG NickServ :identify " +
                                $scope.user.password);
                        }

                        if ($scope.hostToken)
                        {
                            Connection.send("PRIVMSG Jessica :~fixmyip " +
                                $scope.hostToken);
                        }
                    });
                });

                Connection.onMessage(function (message)
                {
                    if (message.indexOf("NOTICE " + $scope.user.nickName +
                        " :Tada") > -1)
                    {
                        if (VERSION == "local")
                        {
                            var test = Channel.register("#test");

                            test.join();

                            $location.path("/channels/#test");
                        } else
                        {
                            var cs = Channel.register("#Coldstorm");
                            var two = Channel.register("#2");

                            cs.join();
                            two.join();

                            $location.path("/channels/#Coldstorm");
                        }
                    }

                    if (message.indexOf("NOTICE " + $scope.user.nickName +
                        "_ :Ghost with your nick has been killed.") > -1 &&
                        mustKill)
                    {
                        Connection.send("NICK " + $scope.user.nickName);
                        Connection.send("PRIVMSG NickServ :IDENTIFY " +
                            $scope.user.password);

                        mustKill = false;

                        if ($scope.hostToken)
                        {
                            Connection.send("PRIVMSG Jessica :~fixmyip " +
                                $scope.hostToken);
                        }
                    }

                    Parser.parse(message);
                });

                Connection.onClose(function ()
                {
                    $scope.connecting = false;
                    if ($scope.connected)
                    {
                        // We were already connected and on the chat view, go back to login
                        $location.path("/login");
                        $scope.reset();
                    }

                    else
                    {
                        if ($scope.port < 85)
                        {
                            $scope.port++;
                            $scope.connect();
                        } else
                        {
                            $rootScope.$apply(function ()
                            {
                                $scope.error = "Couldn't connect to the server";
                            })
                        }
                    }
                });
            }
        };
    }]);
<commit_msg>Switch to /server as soon as the connection opens<commit_after>Controllers.controller("LoginCtrl",
    ["$log", "$scope", "$http", "$rootScope", "$location", "$timeout", "$filter",
    "Connection", "User", "Channel", "Parser",
    function ($log, $scope, $http, $rootScope, $location, $timeout, $filter,
    Connection, User, Channel, Parser)
    {
        var mustKill = false;
        $scope.user = User.get("~");
        $scope.user.nickName = $.cookie("nickName");
        if ($.cookie("color"))
        {
            $scope.user.color = $.cookie("color");
        }

        $location.hash("");

        $http.jsonp("http://geoip.yonom.org/index.php?callback=JSON_CALLBACK")
        .success(function (data)
        {
            $scope.user.country = data.geoplugin_countryName;
            $scope.user.flag = data.geoplugin_countryCode;
        });

        $rootScope.$on("err_nicknameinuse", function (evt)
        {
            if ($scope.user.password)
            {
                Connection.send("NICK " + $scope.user.nickName + "_");

                mustKill = true;

                return;
            }

            Connection.close();
            $scope.reset();

            $rootScope.$apply(function ()
            {
                $scope.error = "Nickname is in use";
            });
        });

        $rootScope.$on("channel.joined", function (evt, channel)
        {
            $scope.connecting = false;
            $scope.connected = false;
            $scope.error = "";
        });

        $scope.reset = function ()
        {
            $scope.connecting = false;
            $scope.connected = false;
            $scope.hostToken = "";

            if (VERSION === "local")
            {
                $scope.port = 81;
            } else
            {
                $scope.port = 82;
            }
            $scope.error = "";
        }

        $scope.login = function ()
        {
            $scope.reset();
            User.register($scope.user.nickName);
            User.alias("~", $scope.user.nickName);

            $http.jsonp("http://kaslai.us/coldstorm/fixip2.php?nick=" +
                encodeURI($scope.user.nickName) + "&random=" +
                Math.floor(Math.random() * 10000000) +
		        "&callback=JSON_CALLBACK").success(function (data)
		        {
		            $scope.hostToken = data.tag;
		        });

            $.cookie("nickName", $scope.user.nickName, { expires: new Date(2017, 00, 01) });
            $.cookie("color", $scope.user.color, { expires: new Date(2017, 00, 01) });

            $scope.connect();
        };

        $scope.connect = function ()
        {
            if ($scope.connecting === false)
            {
                $scope.connecting = true;

                // Attempt to connect
                $log.log("connecting to ws://frogbox.es:" + $scope.port)
                Connection.connect("ws://frogbox.es:" + $scope.port);

                Connection.onOpen(function ()
                {
                    // Connection successfully opened
                    $scope.reset();
                    $scope.connected = true;

                    $location.path("/server");

                    Connection.send("NICK " + $scope.user.nickName);
                    Connection.send("USER " +
                        $scope.user.color.substring(1).toUpperCase() +
                        $scope.user.flag + " - - :New coldstormer");

                    Connection.onWelcome(function ()
                    {
                        if (mustKill)
                        {
                            Connection.send("PRIVMSG NickServ :GHOST " +
                                $scope.user.nickName + " " +
                                $scope.user.password);
                        }

                        if ($scope.user.password)
                        {
                            Connection.send("PRIVMSG NickServ :identify " +
                                $scope.user.password);
                        }

                        if ($scope.hostToken)
                        {
                            Connection.send("PRIVMSG Jessica :~fixmyip " +
                                $scope.hostToken);
                        }
                    });
                });

                Connection.onMessage(function (message)
                {
                    if (message.indexOf("NOTICE " + $scope.user.nickName +
                        " :Tada") > -1)
                    {
                        if (VERSION == "local")
                        {
                            var test = Channel.register("#test");

                            test.join();

                            $location.path("/channels/#test");
                        } else
                        {
                            var cs = Channel.register("#Coldstorm");
                            var two = Channel.register("#2");

                            cs.join();
                            two.join();

                            $location.path("/channels/#Coldstorm");
                        }
                    }

                    if (message.indexOf("NOTICE " + $scope.user.nickName +
                        "_ :Ghost with your nick has been killed.") > -1 &&
                        mustKill)
                    {
                        Connection.send("NICK " + $scope.user.nickName);
                        Connection.send("PRIVMSG NickServ :IDENTIFY " +
                            $scope.user.password);

                        mustKill = false;

                        if ($scope.hostToken)
                        {
                            Connection.send("PRIVMSG Jessica :~fixmyip " +
                                $scope.hostToken);
                        }
                    }

                    Parser.parse(message);
                });

                Connection.onClose(function ()
                {
                    $scope.connecting = false;
                    if ($scope.connected)
                    {
                        // We were already connected and on the chat view, go back to login
                        $location.path("/login");
                        $scope.reset();
                    }

                    else
                    {
                        if ($scope.port < 85)
                        {
                            $scope.port++;
                            $scope.connect();
                        } else
                        {
                            $rootScope.$apply(function ()
                            {
                                $scope.error = "Couldn't connect to the server";
                            })
                        }
                    }
                });
            }
        };
    }]);
<|endoftext|>


<commit_before>var gc = require('../config.js');       // Global Variables.
var err = require(gc.error_object);     // JADS error object.
var fs    = require('fs');              // Node file system object.

// Formats an entry for the directory listing based on whether its a file or dir.
var listingForItem = function (requestURL, filename, fileType) {
    // The template of an entry as well as the indicator variable for a file or dir.
    var listingEntry = '
{itemType}	{itemName}
\n';

    // Populate the template now using the placeholders as text to be replaced.
    listingEntry = listingEntry.replace('{itemType}', fileType);
    listingEntry = listingEntry.replace('{hrefLink}', gc.coreFunctions.joinPaths(requestURL, filename));
    listingEntry = listingEntry.replace('{itemName}', filename);

    // Return the entry.
    return listingEntry;
};

// Reads, formats and returns the directory contents of the path supplied.
exports.returnDirectoryContentsForPath = function (fileSystemPath, httpResponse, requestURL) {
    var i;  // Loop variable used during directory contents looping.

    // Read the file system
    fs.readdir(fileSystemPath, function (error, files) {

        // Setup the variables for the files, dirs and the response body which will contain the HTML page template.
        var fileString = '', dirString = '', responseBody;

        // Read the HTML page template for the directory listing.
        responseBody = '<!DOCTYPE html><title>{dirName}</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Listing of Directory {dirName}

{dirs}

{files}

';

        // Nothing we can do if there is an error.
		if (error) {
			err.newError(500, 'Unable to complete request - server dir unreadable ', undefined, httpResponse, 'jad_directory.js', 'returnDirectoryContentsForPath');
		} else {

            // For each file we retrieve stats about it and then format an output.
			for (i = files.length - 1; i >= 0; i = i - 1) {

                // Retrieve the file statistics.
                fileStats = fs.statSync(fileSystemPath + files[i]);

                // Based on whether it is a file or not we format accordingly.
                if (!fileStats.isFile()) {
                    dirString = dirString + listingForItem(requestURL, files[i], 'Dir');
                } else {
                    fileString = fileString + listingForItem(requestURL, files[i], 'File');
                }
			}

            // Set the content type to HTML.
			httpResponse.setHeader("Content-Type", 'text/html');

            // Inset the dirs and files into the template.
            responseBody = responseBody.replace('{dirs}', dirString);
            responseBody = responseBody.replace('{files}', fileString);

            // Put the title on the page and fill in the page header (hence the greedy flag).
            responseBody = responseBody.replace(new RegExp('{dirName}', 'g'), requestURL);

			// Write the response code
            httpResponse.writeHead(200);
            httpResponse.end(responseBody);
		}

	});
};<commit_msg>Adding support for iOS package installation. if you have an ipa file in a directory during directory listing called xyz.ip the hyperlink will point to xyz.ipa.plist with the itms protocol setup to facilitate the installation of an iOS application.<commit_after>var gc = require('../config.js');       // Global Variables.
var err = require(gc.error_object);     // JADS error object.
var fs    = require('fs');              // Node file system object.

// Formats an entry for the directory listing based on whether its a file or dir.
var listingForItem = function (requestURL, filename, fileType) {

    if (gc.coreFunctions.getReqestExtension(filename) == '.ipa'){
        // The template of an entry as well as the indicator variable for a file or dir.
        // This is used for IPA files that need to be installed on iOS devices so .manifest is appended to search for the appropriate manifest.
        var listingEntry = '
{itemType}	{itemName}
\n';

        // Populate the template now using the placeholders as text to be replaced.
        listingEntry = listingEntry.replace('{itemType}', "iOS");
        listingEntry = listingEntry.replace('{hrefLink}', gc.coreFunctions.joinPaths(requestURL, filename));
        listingEntry = listingEntry.replace('{itemName}', filename);
    } else {
        // The template of an entry as well as the indicator variable for a file or dir.
        var listingEntry = '
{itemType}	{itemName}
\n';
        // Populate the template now using the placeholders as text to be replaced.
        listingEntry = listingEntry.replace('{itemType}', fileType);
        listingEntry = listingEntry.replace('{hrefLink}', gc.coreFunctions.joinPaths(requestURL, filename));
        listingEntry = listingEntry.replace('{itemName}', filename);
    }

    // Return the entry.
    return listingEntry;
};

// Reads, formats and returns the directory contents of the path supplied.
exports.returnDirectoryContentsForPath = function (fileSystemPath, httpResponse, requestURL) {
    var i;  // Loop variable used during directory contents looping.

    // Read the file system
    fs.readdir(fileSystemPath, function (error, files) {

        // Setup the variables for the files, dirs and the response body which will contain the HTML page template.
        var fileString = '', dirString = '', responseBody;

        // Read the HTML page template for the directory listing.
        responseBody = '<!DOCTYPE html><title>{dirName}</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Listing of Directory {dirName}

{dirs}

{files}

';

        // Nothing we can do if there is an error.
        if (error) {
            err.newError(500, 'Unable to complete request - server dir unreadable ', undefined, httpResponse, 'jad_directory.js', 'returnDirectoryContentsForPath');
        } else {

            // For each file we retrieve stats about it and then format an output.
            for (i = files.length - 1; i >= 0; i = i - 1) {

                // Retrieve the file statistics.
                fileStats = fs.statSync(fileSystemPath + files[i]);

                // Based on whether it is a file or not we format accordingly.
                if (!fileStats.isFile()) {
                    dirString = dirString + listingForItem(requestURL, files[i], 'Dir');
                } else {
                    fileString = fileString + listingForItem(requestURL, files[i], 'File');
                }
            }

            // Set the content type to HTML.
            httpResponse.setHeader("Content-Type", 'text/html');

            // Inset the dirs and files into the template.
            responseBody = responseBody.replace('{dirs}', dirString);
            responseBody = responseBody.replace('{files}', fileString);

            // Put the title on the page and fill in the page header (hence the greedy flag).
            responseBody = responseBody.replace(new RegExp('{dirName}', 'g'), requestURL);

            // Write the response code
            httpResponse.writeHead(200);
            httpResponse.end(responseBody);
        }

    });
};<|endoftext|>


<commit_before>import React, { Component, PropTypes, Children } from 'react';
import { render } from 'react-dom';
import { DivIcon, marker } from 'leaflet';
import { MapLayer } from 'react-leaflet';

function createContextProvider(context) {
    class ContextProvider extends Component {
        getChildContext() {
            return context;
        }
        render() {
            return this.props.children;
        }
    }

    ContextProvider.childContextTypes = {};
    Object.keys(context).forEach(key => {
        ContextProvider.childContextTypes[key] = PropTypes.any.isRequired;
    });
    return ContextProvider;
}

export default class Divicon extends MapLayer {
    static propTypes = {
        opacity: PropTypes.number,
        zIndexOffset: PropTypes.number,
    };

    static childContextTypes = {
        popupContainer: PropTypes.object,
    };

    getChildContext() {
        return {
            popupContainer: this.leafletElement,
        }
    }

    // See https://github.com/PaulLeCam/react-leaflet/issues/275
    createLeafletElement(newProps) {
        const { map: _map, layerContainer: _lc, position, ...props } = newProps;
        this.icon = new DivIcon(props);
        return marker(position, { icon: this.icon,  ...props });
    }

    updateLeafletElement(fromProps, toProps) {
        if (toProps.position !== fromProps.position) {
            this.leafletElement.setLatLng(toProps.position);
        }
        if (toProps.zIndexOffset !== fromProps.zIndexOffset) {
            this.leafletElement.setZIndexOffset(toProps.zIndexOffset);
        }
        if (toProps.opacity !== fromProps.opacity) {
            this.leafletElement.setOpacity(toProps.opacity);
        }
        if (toProps.draggable !== fromProps.draggable) {
            if (toProps.draggable) {
                this.leafletElement.dragging.enable();
            }
            else {
                this.leafletElement.dragging.disable();
            }
        }
    }

    componentWillMount() {
        super.componentWillMount();
        this.leafletElement = this.createLeafletElement(this.props);
    }
    componentDidMount() {
        super.componentDidMount();
        this.renderComponent();
    }
    componentDidUpdate(fromProps) {
        this.renderComponent();
        this.updateLeafletElement(fromProps, this.props);
    }

    renderComponent = () => {
        const ContextProvider = createContextProvider({ ...this.context, ...this.getChildContext() });
        const container = this.leafletElement._icon;
        const component = (
            <ContextProvider>
                {this.props.children}
            </ContextProvider>
        );
        if(container) {
            render(
                component,
                container
            );
        }
    }

    render() {
        return null;
    }

}

<commit_msg>not making all context types required<commit_after>import React, { Component, PropTypes, Children } from 'react';
import { render } from 'react-dom';
import { DivIcon, marker } from 'leaflet';
import { MapLayer } from 'react-leaflet';

function createContextProvider(context) {
    class ContextProvider extends Component {
        getChildContext() {
            return context;
        }
        render() {
            return this.props.children;
        }
    }

    ContextProvider.childContextTypes = {};
    Object.keys(context).forEach(key => {
        ContextProvider.childContextTypes[key] = PropTypes.any;
    });
    return ContextProvider;
}

export default class Divicon extends MapLayer {
    static propTypes = {
        opacity: PropTypes.number,
        zIndexOffset: PropTypes.number,
    };

    static childContextTypes = {
        popupContainer: PropTypes.object,
    };

    getChildContext() {
        return {
            popupContainer: this.leafletElement,
        }
    }

    // See https://github.com/PaulLeCam/react-leaflet/issues/275
    createLeafletElement(newProps) {
        const { map: _map, layerContainer: _lc, position, ...props } = newProps;
        this.icon = new DivIcon(props);
        return marker(position, { icon: this.icon,  ...props });
    }

    updateLeafletElement(fromProps, toProps) {
        if (toProps.position !== fromProps.position) {
            this.leafletElement.setLatLng(toProps.position);
        }
        if (toProps.zIndexOffset !== fromProps.zIndexOffset) {
            this.leafletElement.setZIndexOffset(toProps.zIndexOffset);
        }
        if (toProps.opacity !== fromProps.opacity) {
            this.leafletElement.setOpacity(toProps.opacity);
        }
        if (toProps.draggable !== fromProps.draggable) {
            if (toProps.draggable) {
                this.leafletElement.dragging.enable();
            }
            else {
                this.leafletElement.dragging.disable();
            }
        }
    }

    componentWillMount() {
        super.componentWillMount();
        this.leafletElement = this.createLeafletElement(this.props);
    }
    componentDidMount() {
        super.componentDidMount();
        this.renderComponent();
    }
    componentDidUpdate(fromProps) {
        this.renderComponent();
        this.updateLeafletElement(fromProps, this.props);
    }

    renderComponent = () => {
        const ContextProvider = createContextProvider({ ...this.context, ...this.getChildContext() });
        const container = this.leafletElement._icon;
        const component = (
            <ContextProvider>
                {this.props.children}
            </ContextProvider>
        );
        if(container) {
            render(
                component,
                container
            );
        }
    }

    render() {
        return null;
    }

}

<|endoftext|>