
/*---------------------------------------------------------------------------*/
/* appclient_main.cpp                                                        */
/* copyright (c) innovaphone 2012                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#define APPCLIENT_MAIN_CPP_

#include <ctype.h>
#include <arpa/inet.h>
#include "platform/platform.h"

#include "common/build/release.h"
#include "common/os/iomux.h"
#include "common/interface/dns.h"
#include "common/interface/time.h"
#include "common/interface/socket.h"
#include "common/interface/random.h"
#include "common/interface/task.h"
#include "common/interface/stask.h"
#include "common/interface/audio.h"
#include "common/interface/ringer.h"
#include "common/interface/media.h"
#include "common/interface/appsharing.h"
#include "common/interface/certificates.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/json_api.h"
#include "common/interface/file.h"
#include "common/interface/websocket_client.h"
#include "common/interface/database.h"
#include "common/interface/http_client.h"
#include "common/interface/process.h"
#include "common/interface/local_storage.h"
#include "common/interface/ethernet.h"
#include "common/interface/url_handler.h"
#include "common/interface/call_kit.h"
#include "common/interface/notifications-if.h"
#include "common/interface/launcherapi.h"
#include "common/interface/search_contact.h"
#include "common/interface/remote_services.h"
#include "common/interface/signaling_app.h"
#include "common/interface/client.h"
#include "common/ilib/json.h"
#include "common/ilib/base64.h"
#include "common/ilib/str.h"
#include "common/ilib/http_query_args.h"

#include "common/service/webserver_shared/webserver_internal.h"
#include "common/lib/httplib.h"
#include "common/lib/appservice.h"
#include "common/lib/appwebsocket.h"
#include "common/lib/apiwebsocket_client.h"

#include "common/service/openssl_shared/ssl.h"

#include "webserver/types.h"
#include "webserver/apparguments.h"
#include "common/service/webserver_shared/http_auth_digest.h"
#include "webserver/http_method_handler.h"
#include "common/service/webserver_shared/http_digest_host.h"
#include "webserver/http_connection.h"
#include "webserver/http_datasource.h"
#include "webserver/http_vfs_entry.h"
#include "webserver/webserver.h"

#include "common/lib/tasks_file.h"

#include "client/url_handler/url_handler.h"
#include "common/lib/language.h"
#include "client/lang/myapps_languages.h"
#include "launcherapi/launcherapihandler.h"
#include "appproxy/appproxy_if.h"
#include "appproxy/appproxy.h"
#if (defined(__ANDROID__) || (defined(_IOS_) && !TARGET_OS_IPHONE))
#include "client/updateservicehandler/updateservicehandler.h"
#endif
#include "client/logflags/debug_logflags.h"
#include "client/config/client_config.h"
#include "appclient_config.h"
#include "client/config/debug_readlog.h"
#include "client/config/client.h"
#include "apirelay/apirelay.h"
#include "client/media/appclient_media.h"
#include "client/ringer/appclient_ringer.h"

#include "common/linux/udp/udpsocket.h"
#include "common/linux/tcp/tcpsocket.h"
#include "common/linux/uds/udsocket.h"
#include "client_sockets.h"
#include "appclient_main.h"

#define FILE_ "appclient_main.cpp"

extern const char *get_files_dir();
extern char get_separator_char();
extern void copy_to_external(const char *file_name);

#include "sdk/common/lib/app_updates.h"
#include "sdk/common/interface/appwebsocket_client.h"
#include "sdk/common/interface/appwebsocket_authenticator.h"
#include "common/service/appwebsocket_client/appwebsocket_client.h"
#include "signaling/signaling_api.h"
#include "client/signaling_app/signaling_app.h"

USING_NAMESPACE

#if defined(__ANDROID__)
const char * _PRODUCT_NAME_ = "myApps for Android";
#elif defined(_IOS_)
#if TARGET_OS_IPHONE
const char * _PRODUCT_NAME_ = "myApps for iOS";
#else
const char * _PRODUCT_NAME_ = "myApps for macOS";
#endif
#elif defined(_WINDOWS)
const char * _PRODUCT_NAME_ = "myApps for Windows";
#else
const char * _PRODUCT_NAME_ = "myApps for Linux";
#endif

/*---------------------------------------------------------------------------*/

class LauncherApiHandlerService * launcherapiService;

/*---------------------------------------------------------------------------*/

class ContactsSearchApiProvider * contactsSearchApiProvider;

class ContactsSearchApiProvider : public ApiProvider
{
    class UAppClientMain * appClientMainUser;

    void ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base) override;
    void ApiProviderConsumerClosed(const char * client, const char * consumer) override;
    int CreateRef(char * ref, const char * client, const char * consumer, const char * src);

public:
    ContactsSearchApiProvider(class UAppClientMain * appClientMainUser);
    ~ContactsSearchApiProvider();

    void ExtractRef(char * p, const char ** client, const char ** consumer, const char ** src);
};

ContactsSearchApiProvider::ContactsSearchApiProvider(class UAppClientMain * appClientMainUser) : ApiProvider(launcherapiService->launcherApi, "com.innovaphone.search", "Local Contacts")
{
    this->appClientMainUser = appClientMainUser;
}

ContactsSearchApiProvider::~ContactsSearchApiProvider()
{
}

int ContactsSearchApiProvider::CreateRef(char * ref, const char * client, const char * consumer, const char * src)
{
    if (!ref) {
        return ((client ? 2 + strlen(client) : 1) + (consumer ? 2 + strlen(consumer) : 1) + (src ? 2 + strlen(src) : 1));
    }
    int i = 0;
    if (client) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", client);
    }
    ref[i++] = '\n';
    if (consumer) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", consumer);
    }
    ref[i++] = '\n';
    if (src) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", src);
    }
    ref[i] = '\0';
    return i;
}

void ContactsSearchApiProvider::ExtractRef(char * p, const char ** client, const char ** consumer, const char ** src)
{
    *client = (p[0] == ' ') ? p + 1 : 0;
    p = strchr(p, '\n');
    *(p++) = '\0';
    *consumer = (p[0] == ' ') ? p + 1 : 0;
    p = strchr(p, '\n');
    *(p++) = '\0';
    *src = (p[0] == ' ') ? p + 1 : 0;
}

void ContactsSearchApiProvider::ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base)
{
    const char * mt = msg.get_string(base, "mt");
    if (!strcmp(mt, "Search")) {
        const char * search = msg.get_string(base, "search");
        if (search) {
            char * ref = (char *)(malloc(CreateRef(NULL, client, consumer, src) + 1));
            CreateRef(ref, client, consumer, src);
            appClientMainUser->AppClientMainContactsSearch(ref, search);
            free(ref);
        }
    }
}

void ContactsSearchApiProvider::ApiProviderConsumerClosed(const char * client, const char * consumer)
{
}

void phone_contacts_search(const char *ref, const char *info)
{
    if (launcherapiService->launcherApi) {
        char * ref_buffer = _strdup(ref);
        const char * client;
        const char * consumer;
        const char * src;
        contactsSearchApiProvider->ExtractRef(ref_buffer, &client, &consumer, &src);
        contactsSearchApiProvider->ApiProviderSendJson(client, consumer, src, info);
        free(ref_buffer);
    }
}

/*---------------------------------------------------------------------------*/

class ContactsLookupApiProvider * contactsLookupApiProvider;

class ContactsLookupApiProvider : public ApiProvider
{
    class UAppClientMain * appClientMainUser;

    void ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base) override;
    void ApiProviderConsumerClosed(const char * client, const char * consumer) override;
    int CreateRef(char * ref, const char * client, const char * consumer, const char * src);

public:
    ContactsLookupApiProvider(class UAppClientMain * appClientMainUser);
    ~ContactsLookupApiProvider();

    void ExtractRef(char * p, const char ** client, const char ** consumer, const char ** src);
};

ContactsLookupApiProvider::ContactsLookupApiProvider(class UAppClientMain * appClientMainUser) : ApiProvider(launcherapiService->launcherApi, "com.innovaphone.phonelookup", "Local Contacts")
{
    this->appClientMainUser = appClientMainUser;
}

ContactsLookupApiProvider::~ContactsLookupApiProvider()
{
}

int ContactsLookupApiProvider::CreateRef(char * ref, const char * client, const char * consumer, const char * src)
{
    if (!ref) {
        return ((client ? 2 + strlen(client) : 1) + (consumer ? 2 + strlen(consumer) : 1) + (src ? 2 + strlen(src) : 1));
    }
    int i = 0;
    if (client) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", client);
    }
    ref[i++] = '\n';
    if (consumer) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", consumer);
    }
    ref[i++] = '\n';
    if (src) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", src);
    }
    ref[i] = '\0';
    return i;
}

void ContactsLookupApiProvider::ExtractRef(char * p, const char ** client, const char ** consumer, const char ** src)
{
    *client = (p[0] == ' ') ? p + 1 : 0;
    p = strchr(p, '\n');
    *(p++) = '\0';
    *consumer = (p[0] == ' ') ? p + 1 : 0;
    p = strchr(p, '\n');
    *(p++) = '\0';
    *src = (p[0] == ' ') ? p + 1 : 0;
}

void ContactsLookupApiProvider::ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base)
{
    const char * mt = msg.get_string(base, "mt");
    if (!strcmp(mt, "Lookup")) {
        const char * number = msg.get_string(base, "lookup");
        const char * prefixIntl = msg.get_string(base, "prefixIntl");
        const char * prefixNtl = msg.get_string(base, "prefixNtl");
        const char * prefixSubs = msg.get_string(base, "prefixExt");
        const char * areaCode = msg.get_string(base, "area");
        const char * countryCode = msg.get_string(base, "country");
        if (number) {
            char * ref = (char *)(malloc(CreateRef(NULL, client, consumer, src) + 1));
            CreateRef(ref, client, consumer, src);
            appClientMainUser->AppClientMainContactsLookup(ref, number, prefixIntl, prefixNtl, prefixSubs, areaCode, countryCode);
            free(ref);
        }
    }
}

void ContactsLookupApiProvider::ApiProviderConsumerClosed(const char * client, const char * consumer)
{
}

void phone_contacts_lookup(const char *ref, const char *info)
{
    if (launcherapiService->launcherApi) {
        char * ref_buffer = _strdup(ref);
        const char * client;
        const char * consumer;
        const char * src;
        contactsLookupApiProvider->ExtractRef(ref_buffer, &client, &consumer, &src);
        contactsLookupApiProvider->ApiProviderSendJson(client, consumer, src, info);
        free(ref_buffer);
    }
}

/*---------------------------------------------------------------------------*/

class TaskGetCertMobileconfig : public UWebserverGet {
    void WebserverGetRequestAcceptComplete(IWebserverGet * const webserverGet) override;
    void WebserverGetSendResult(IWebserverGet * const webserverGet) override;
    void WebserverGetCloseComplete(IWebserverGet * const webserverGet) override;

    class IClient * client;
    class UAppClientMain * appClientMainUser;
    byte * fixCert;
    size_t fixCertLen;
    class IWebserverGet * get;

public:
    TaskGetCertMobileconfig(class IClient * client, class UAppClientMain * appClientMainUser, const byte * fixCert, size_t fixCertLen);
    ~TaskGetCertMobileconfig();
};

TaskGetCertMobileconfig::TaskGetCertMobileconfig(class IClient * client, class UAppClientMain * appClientMainUser, const byte * fixCert, size_t fixCertLen)
{
    this->client = client;
    this->appClientMainUser = appClientMainUser;
    this->fixCertLen = fixCertLen;
    this->fixCert = NULL;
    if (fixCert) {
        this->fixCert = (byte *)(malloc(fixCertLen));
        memcpy(this->fixCert, fixCert, fixCertLen);
    }
    get = NULL;
}

TaskGetCertMobileconfig::~TaskGetCertMobileconfig()
{
    if (fixCert) free(fixCert);
	client->WebserverRequestClosed();
}

void TaskGetCertMobileconfig::WebserverGetRequestAcceptComplete(IWebserverGet * const webserverGet)
{
    get = webserverGet;
    get->SetTransferInfo(WSP_RESPONSE_MOBILECONFIG, WS_RESPONSE_CHUNKED);
    if (fixCert) {
        int len = appClientMainUser->AppClientMainCertMobileconfig(NULL, fixCert, fixCertLen);
        byte * buf = (byte *)(malloc(len));
        appClientMainUser->AppClientMainCertMobileconfig(buf, fixCert, fixCertLen);
        get->Send((void *)buf, len);
        free(buf);
    }
    else {
        get->Close();
    }
}

void TaskGetCertMobileconfig::WebserverGetSendResult(IWebserverGet * const webserverGet)
{
    if (get) get->Close();
    else delete this;
}

void TaskGetCertMobileconfig::WebserverGetCloseComplete(IWebserverGet * const webserverGet)
{
    delete webserverGet;
    get = 0;
    delete this;
}

/*---------------------------------------------------------------------------*/

class TaskWebserverGet : public UWebserverGet {
    void WebserverGetRequestAcceptComplete(IWebserverGet * const webserverGet) override;
    void WebserverGetSendResult(IWebserverGet * const webserverGet) override;
    void WebserverGetCloseComplete(IWebserverGet * const webserverGet) override;

    char * resourceName;

public:
    TaskWebserverGet(class IClient * client, class UAppClientMain * appClientMainUser, const char * resourceName);
    ~TaskWebserverGet();

    class IClient * client;
    class UAppClientMain * appClientMainUser;
    class IWebserverGet * get;
    bool sending;
    bool complete;
};

TaskWebserverGet::TaskWebserverGet(class IClient * client, class UAppClientMain * appClientMainUser, const char * resourceName)
{
    this->client = client;
    this->appClientMainUser = appClientMainUser;
    this->resourceName = _strdup(resourceName);
    get = NULL;
    sending = false;
    complete = false;
}

TaskWebserverGet::~TaskWebserverGet()
{
    if (resourceName) free(resourceName);
	client->WebserverRequestClosed();
}

void TaskWebserverGet::WebserverGetRequestAcceptComplete(IWebserverGet * const webserverGet)
{
    char buf[20];
    get = webserverGet;
    wsr_type_t type = GetResponseTypeForFileName(resourceName);
    if (type == WSP_RESPONSE_NO_TYPE) {
        type = WSP_RESPONSE_BINARY;
    }
    get->SetTransferInfo(type, WS_RESPONSE_CHUNKED);
    _sprintf(buf, "%p", this);
    appClientMainUser->AppClientMainWebserverGet(buf, resourceName);
}

void TaskWebserverGet::WebserverGetSendResult(IWebserverGet * const webserverGet)
{
    char buf[20];
    sending = false;
    _sprintf(buf, "%p", this);
    appClientMainUser->AppClientMainWebserverGetSent(buf);
    if (complete) {
        if (get) get->Close();
        else delete this;
    }
}

void TaskWebserverGet::WebserverGetCloseComplete(IWebserverGet * const webserverGet)
{
    char buf[20];
    delete webserverGet;
    get = 0;
    if (sending) {
        _sprintf(buf, "%p", this);
        appClientMainUser->AppClientMainWebserverGetSent(buf);
    }
    if (complete) {
        delete this;
    }
}

void phone_webserver_get(const char *context, bool complete, int length, const byte *data)
{
    TaskWebserverGet *taskWebserverGet;
    uintp ctx;
    const char *c;
    char buf[20];

    ctx = 0;
    c = context;
    while (*c) {
        if (isxdigit(*c)) {
            ctx = (ctx << 4) | (isdigit(*c) ? *c - '0' : tolower(*c) - 'a' + 10);
        }
        c++;
    }
	taskWebserverGet = (TaskWebserverGet *)ctx;
    taskWebserverGet->complete = complete;
    if (taskWebserverGet->get) {
        taskWebserverGet->get->Send(data, length);
        taskWebserverGet->sending = true;
    }
    else {
        _sprintf(buf, "%p", taskWebserverGet);
        taskWebserverGet->appClientMainUser->AppClientMainWebserverGetSent(buf);
        if (complete) {
            if (taskWebserverGet->get) taskWebserverGet->get->Close();
            else delete taskWebserverGet;
        }
    }
}

/*---------------------------------------------------------------------------*/

class SignalingAppWebsocketClientProvider : public IAppWebsocketClientProvider {
    IAppWebsocketClient * Create(class UAppWebsocketClient * const user, class IInstanceLog * const log) override;
    class IIoMux * iomux;
    class ISocketProvider * tcpSocketProvider;
    class ISocketProvider * tlsSocketProvider;
    class IDns * dns;
    istd::list<class SignalingAppWebsocketClient> appWebsocketClients;

public:
    SignalingAppWebsocketClientProvider(class IIoMux * const iomux,
                                        class ISocketProvider * const tcpSocketProvider,
                                        class ISocketProvider * const tlsSocketProvider,
                                        class IDns * const dns);

    void Ping();
};

class SignalingAppWebsocketClient : public AppWebsocketClient, public istd::listElement<SignalingAppWebsocketClient> {
friend class SignalingAppWebsocketClientProvider;
    bool isConnected;

    virtual void WebsocketConnectComplete(IWebsocketClient * const websocket) override; 
public:
    SignalingAppWebsocketClient(class IIoMux * const iomux, class UAppWebsocketClient * user, class ISocketProvider * const tcpSocketProvider, class ISocketProvider * const tlsSocketProvider, class IDns * const dns, class IInstanceLog * log);
};


/*---------------------------------------------------------------------------*/

SignalingAppWebsocketClientProvider * CreateSignalingAppWebsocketClientProvider(class IIoMux * const iomux,
                                                                                class ISocketProvider * const tcpSocketProvider,
                                                                                class ISocketProvider * const tlsSocketProvider,
                                                                                class IDns * const dns)
{
    return new SignalingAppWebsocketClientProvider(iomux, tcpSocketProvider, tlsSocketProvider, dns);
}

SignalingAppWebsocketClientProvider::SignalingAppWebsocketClientProvider(class IIoMux * const iomux,
                                                                         class ISocketProvider * const tcpSocketProvider,
                                                                         class ISocketProvider * const tlsSocketProvider,
                                                                         class IDns * const dns)
{
    this->iomux = iomux;
    this->tcpSocketProvider = tcpSocketProvider;
    this->tlsSocketProvider = tlsSocketProvider;
    this->dns = dns;
}

IAppWebsocketClient * SignalingAppWebsocketClientProvider::Create(class UAppWebsocketClient * const user, class IInstanceLog * const log)
{
    SignalingAppWebsocketClient *tmp = new SignalingAppWebsocketClient(iomux, user, tcpSocketProvider, tlsSocketProvider, dns, log);
    appWebsocketClients.push_back(tmp);
    return tmp;
}

void SignalingAppWebsocketClientProvider::Ping()
{
    SignalingAppWebsocketClient *c;
    if(!appWebsocketClients.empty()) {
        for (c = appWebsocketClients.front(); c; c = c->goNext()) {
            if (c->client && c->isConnected) {
                c->client->Ping();
            }
        }
    }
}

SignalingAppWebsocketClient::SignalingAppWebsocketClient(class IIoMux * const iomux, class UAppWebsocketClient * user, class ISocketProvider * const tcpSocketProvider, class ISocketProvider * const tlsSocketProvider, class IDns * const dns, class IInstanceLog * log) :
    AppWebsocketClient(iomux, user, tcpSocketProvider, tlsSocketProvider, dns, log)
{
    isConnected = false;
}

void SignalingAppWebsocketClient::WebsocketConnectComplete(IWebsocketClient * const websocket)
{
    isConnected = true;
    AppWebsocketClient::WebsocketConnectComplete(websocket);
}

/*---------------------------------------------------------------------------*/

#define CONFIG_FILE_NAME "config.json"
#define MAX_CONFIG_LENGTH 64000

static const int __EXTERNAL_APPS_API_TRY_CLOSE = 3;

class ExternalApp : public istd::listElement<ExternalApp> {
public:
    ExternalApp(int id, const char * name, const char * path, const char * param, bool autostart) {
        this->id = id;
        this->name = _strdup(name);
        this->path = _strdup(path);
        this->param = _strdup(param);
        this->autostart = autostart;
    }
    ~ExternalApp() {
        if(this->name) free(this->name);
        if(this->path) free(this->path);
        if(this->param) free(this->param);
    }

    void Update(const char * name, const char * path, const char * param, bool autostart) {
        if(this->name) free(this->name);
        this->name = _strdup(name);
        if(this->path) free(this->path);
        this->path = _strdup(path);
        if(this->param) free(this->param);
        this->param = _strdup(param);
        this->autostart = autostart;
    }

    int id;
    char * name;
    char * path;
    char * param;
    bool autostart;
};

class ExternalApps : public ApiWebsocketClient, public UIoExec {
    friend class ExternalAppsApiProvider;
    void ApiWebsocketClientCloseComplete() override;
    void ApiProviderCloseComplete();

    bool apiWebsocketClientClosed;
    bool closed;
    bool stopping;
    int execCnt;

    class ExternalAppsApiProvider * external;
    class IIoMux * const iomux;
    class IInstanceLog * const log;
    class Browser * const browser;

    void ExternalApiProviderClosed();

    void IoExec(void * execContext) override;

public:
    ExternalApps(class Browser * const browser, class IIoMux * const iomux, class IInstanceLog * const log, class ISocketProvider * localSocketProvider, const char * pwd, const char * dn, const char * localSocketPath);
    ~ExternalApps();

    void UpdateExternalApps(const char * externalApps);

    void TryClose();
};

class ExternalAppsApiProviderHelper : public istd::listElement<ExternalAppsApiProviderHelper>
{
public:
    ExternalAppsApiProviderHelper(const char * client, const char * consumer, const char * src) {
        this->client = _strdup(client);
        this->consumer = _strdup(consumer);
        this->src = _strdup(src);
    }

    ~ExternalAppsApiProviderHelper() {
        if(this->client) free(this->client);
        if(this->consumer) free(this->consumer);
        if(this->src) free(this->src);
    }

    char * client;
    char * consumer;
    char * src;
};

class ExternalAppsHelper : public istd::listElement<ExternalAppsHelper>
{
public:
    ExternalAppsHelper(class ExternalAppsApiProvider * provider, int id, const char * path, const char * param) {
        this->provider = provider;
        this->path = _strdup(path);
        this->param = _strdup(param);
        this->id = id;
    }

    ~ExternalAppsHelper() {
        if(this->path) free(this->path);
        if(this->param) free(this->param);
    }

    class ExternalAppsApiProvider * provider;
    char * path;
    char * param;
    int id;
};

class ExternalAppsApiProvider : public ApiProvider
{
    friend class ExternalApps;
    void ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base) override;
    void ApiProviderConsumerClosed(const char * client, const char * consumer) override;

    class IInstanceLog * const log;
    ExternalApps * parent;
    char * externalAppsStr;

    unsigned int execCnt;
    bool stopping;

    istd::list<class ExternalAppsApiProviderHelper> consumers;
    istd::list<class ExternalAppsHelper> startExternalApps;

    void UpdateExternalApps(const char * client, const char * consumer, const char * src);

public:
    ExternalAppsApiProvider(class ExternalApps * parent, class IInstanceLog * const log);
    ~ExternalAppsApiProvider();

    void UpdateConsumers(const char * externalApps);
    void TryClose();
};

typedef std::list<class ILauncherApi *> LauncherApis;
typedef std::list<class UNotifications *> NotificationsUsers;

class AppClientWriteConfig {};

class AppClientConnectingTimer : public ITimer {
public:
    AppClientConnectingTimer(IIoMux * ioMux, UTimerSubclass<AppClientConnectingTimer> * owner) : ITimer(ioMux, owner) {};
};

class Browser : public ULauncherApi, public IInstanceLog, public IShutdownHandler, public UTask, public STaskContext, public UClient, public USignalingApp,
    public ICallKit, public INotifications, public UNotifications, public UAppProxy,
    public UIoExecSubclass<AppClientWriteConfig>,
    public UTimerSubclass<AppClientConnectingTimer>,
    public UClientConfigItems,
    public ClientConfigUser
{
    void SetExec(IIoMux * iomux, UIoExecSubclass<AppClientWriteConfig> * ioContext, AppClientWriteConfig * execContext) { iomux->SetExec(ioContext, execContext); };
    void IoExec(AppClientWriteConfig * context);

    IIoMux * iomux;
    UAppClientMain * appClientMainUser;
    class DebugLogFlags * debugLogFlags;
    class IClient * client;
    class IAppProxy * appproxy;
    class IAppClientMedia * mediaClient;
    class ISignalingConfig * signalingConfig;
    char *url;
    char *webserverUrl;
    char *webserverPassword;
    char *proxy;
    unsigned proxyLen;
    char *startFixCertUrl;
    byte *fixCert;
    size_t fixCertLen;
    char *autostartSettingsApp;
    char *appClientConnectionUrl;
    AppClientConnectingTimer appClientConnectingTimer;
    bool appClientConnected;
    unsigned registrationCheckRequests;
    class TaskFileWrite * write;
    bool writeConfig;
    bool writeConfigScheduled;
    bool stopping;
    word readConfigBase;
    char readConfigBuffer[MAX_CONFIG_LENGTH];
    class json_io readConfigJson;

    ClientConfig clientConfig;
    void ClientConfigItemAdded(IClientConfigItem * item) override;
    void ClientConfigItemSet(IClientConfigItem * item) override;
    void Apply(ClientConfigLogFlags * item) override;
    void Apply(ClientConfigNoHexdump * item) override;
    void Apply(ClientConfigOrientationAdaptationOff * item) override;
    void Apply(ClientConfigDisplayOnIfCharging * item) override;
    void Apply(ClientConfigNoPushAutostart * item) override;
    void Apply(ClientConfigAudioApi * item) override;
    void Apply(ClientConfigBluetoothHeadsetMode * item) override;
    void Apply(ClientConfigPhoneAccountEnabled * item) override;
    void Apply(ClientConfigRingInHeadset * item) override;
    void Apply(ClientConfigActiveAfterClose * item) override;
    void Apply(ClientConfigActiveAfterCloseAsked * item) override;
    void Apply(ClientConfigAddressBookInaccessibleWarned * item) override;
    void Apply(ClientConfigPushNotifications * item) override;
    void Apply(ClientConfigPushPassword * item) override;
    void Apply(ClientConfigDownloadedBuild * item) override;
    void Apply(ClientConfigNoVpnAddresses * item) override;
    void Apply(ClientConfigShowDesktopNotifications * item) override;
    void Apply(ClientConfigAutostart * item) override;
    void Apply(ClientConfigDocking * item) override;
    void Apply(ClientConfigAutoOffline * item) override;
    void Apply(ClientConfigHotkeyDial * item) override;
    void Apply(ClientConfigHotkeyAccept * item) override;
    void Apply(ClientConfigHotkeyReject * item) override;
    void Apply(ClientConfigHotkeyAppLink0 * item) override;
    void Apply(ClientConfigHotkeyAppLink1 * item) override;
    void Apply(ClientConfigHotkeyAppLink2 * item) override;
    void Apply(ClientConfigRecordingUrl * item) override;
    void Apply(ClientConfigIsRecordingByDefaultOn * item) override;
    void Apply(ClientConfigRecordExternalOnly * item) override;
    void Apply(ClientConfigAvoidNativeCallScreen * item) override;
    void Apply(ClientConfigHideFromNativeCallList * item) override;
    void Apply(ClientConfigCurrentUserSip * item) override;
    void Apply(ClientConfigLocalStorage * item) override;

    istd::list<class ExternalApp> externalApps;
    class ExternalApps * externalAppsApi;
    char * externalAppsStr;

    const char * GetAppName() const { return "browser"; }
    const char * GetAppDomain() const { return ""; }

    void TimerOnTimeout(AppClientConnectingTimer * timer);

    void TaskComplete(class ITask * const task) override;
    void TaskFailed(class ITask * const task) override;
    void TaskProgress(class ITask * const task, dword progress) override;

    void Shutdown() override;
    void ShutdownTimeout() override;
    void TryStop();

    void ReadConfig();
    void WriteConfig();

    void AppProxyReady() override;
    void AppProxyConnectionMonitor(const char *url, bool up) override;

    void CxUse(bool use, const char *ringtone, bool supportsVideo, int maximumCallGroups, int maximumCallsPerCallGroup) override;
    void CxReportNewIncomingCall(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
        bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping) override;
    void CxReportOutgoingCallConnecting(const char *uuid) override;
    void CxReportOutgoingCallConnected(const char *uuid) override;
    void CxReportCallUpdated(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
        bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping) override;
    void CxReportCallEnded(const char *uuid, CxCallEndedReason reason) override;
    void CxRequestStartCall(const char *uuid, const char *number, const char *name, bool video) override;
    void CxRequestEndCall(const char *uuid) override;
    void CxRequestSetHeldCall(const char *uuid, bool held) override;
    void CxRequestSetMutedCall(const char *uuid, bool muted) override;
    void CxRequestSetGroupCall(const char *uuid, const char *with) override;
    void CxRequestPlayDTMF(const char *uuid, const char *digits) override;

    void AddLocalNotification(const char* ref, LocalNotificationType type, const char* title, const char* text, const char* smallIcon, const char* largeIcon,
        const char* soundUrl, const char* buttonTitle, const char* acceptTitle, const char* rejectTitle, const char* replyTitle, const char * link, UNotifications * user) override;
    void RemoveLocalNotification(const char* ref) override;
    void ShowUpdateNotification(const char * title, const char * text, bool showAtOnce) override {}
    void SetShowDesktopNotifications(bool showDesktopNotifications) override {}

    void LocalNotificationClicked(const char* ref, enum LocalNotificationAction action, const char* replyText);

    void LauncherApiClosing(class ILauncherApi * launcherApi) override;
    void LauncherApiDialGsm(const char * number) override;
    void LauncherApiDisablePowerSaving(bool active);
    void LauncherApiAppWindowOpened(const char * app, const char * title);
    void LauncherApiSetResize(const char * app, int width, int height);
    void LauncherApiSetMaximized(const char * app, bool on);
    void LauncherApiRegistrationState(bool up) override;
    void LauncherApiShowIfLocked(const char * ref, bool show) override;
    void LauncherApiShowUnlock() override;
    void LauncherApiAddLocalNotification(const char * ref, LocalNotificationType type, const char * title, const char * text, const char * smallIcon, const char * largeIcon,
        const char * soundUrl, const char * buttonTitle, const char * acceptTitle, const char * rejectTitle, const char * replyTitle, const char * link, void * ctx) override { AddLocalNotification(ref, type, title, text, smallIcon, largeIcon, soundUrl, buttonTitle, acceptTitle, rejectTitle, replyTitle, link, this); }
    void LauncherApiRemoveLocalNotification(const char * ref) override                                              { RemoveLocalNotification(ref); }
    void LauncherApiSetBadgeCount(const char * ref, unsigned count, const char * text) override;
    void LauncherApiStatusBarAppearance(bool hidden, bool blackText, dword color) override;
    void LauncherApiCacheable(const char * url, const char * filename) override;
    void LauncherApiSetPushPassword(const char * pwd) override;
    void LauncherApiSetDownloadedBuild(const char * build) override;
    void LauncherApiSoftwareUpdate() override;
    void LauncherApiNoPushAutostart(bool noPushAutostart) override;
    void LauncherApiSetLangId(const char* langId) override;
    void LauncherApiSetAdminSettings(class json_io& model, word baseSettings) override;
    void LauncherApiSetUser(const char * sip) override;
    void LauncherApiAppClientUrl(const char * url) override;
    void LauncherApiClose() override;
    void LauncherApiRccAppConnected(bool up) override;
    void LauncherApiSetConnInfo(bool up, bool standby, const char * cur, const char * pri, const char * sec, const char * master);
    void SetAdminSettings(class json_io & json, word base, bool pbx, void * context);
    void SetClose(bool closeSupported);
    void SetDocking(bool docked);

    UWebserverGet * ClientWebserverGetAccept(const char * resourceName) override;
    void ClientConfigOptionsToJson(class json_io & json, word base, char * & tmp) override;
    void ClientConfigToJson(class json_io & json, word base, char * & tmp) override;
    void ClientJsonToConfig(class json_io & json, word base, void * context) override;
    void ClientGetLogActive(bool active) override;
    void ClientFixCert() override;
    bool ClientEMailLogAvailable() override;
    void ClientEMailLog() override;
    void ClientClearLog() override;
    void ClientAutostartSettingsApp(char * name, size_t name_len) override;
    void ClientStartAutostartSettingsApp(const char *name) override;

    void SignalingRegistrationState(bool up) override;
    void SignalingCallsActive(bool active) override;

    void SetExternalApp(int id, const char * name, const char * path, const char * param, bool autostart);
    void DelExternalApp(int id);
    void UpdateExternalApps();
    
public:
    Browser(class IIoMux * iomux, class UAppClientMain * appClientMainUser, class DebugLogFlags * debugLogFlags);
    ~Browser();

    UCallKit * callKitUser;
    LauncherApis launcherApis;
    class ISignalingApp * signalingApp;
    NotificationsUsers notificationsUsers;

    void SetClient(class IClient * client) { this->client = client; };
    void SetAppProxy(class IAppProxy * appproxy, const char * proxy);
    void SetUrl(const char *url, const char *webserverUrl, const char *webserverPassword, const char *startFixCertUrl, const byte *fixCert, size_t fixCertLen);
    void SetApiClientParameters(class ISocketProvider * localSocketProvider, class ISocketProvider * tcp, class ISocketProvider * tls, class IDns * dns, const char * apiRelayUrl, const char * servicePassword, const char * apiRelayPath);
    void ExternalAppsApiClientCloseComplete();
    void AddIAppClientMedia(class IAppClientMedia * const mediaClient);
    void AddISignalingConfig(class ISignalingConfig* const signalingConfig);

    void SetAutostartSettingsApp(const char *name);
    void SetActiveAfterClose(bool active);
    void SetActiveAfterCloseAsked(bool asked);
    void SetAddressBookInaccessibleWarned(bool warned);
    void PushNotification();
    void SetPhoneAccountEnabled(bool enabled);
    void SetLocalStorageItem(const char * id, const char * val);
    char * GetLocalStorageItem(const char * id);
    void AddCallKitUser(class UCallKit * callKitUser) { this->callKitUser = callKitUser; }
    void AddNotificationsUser(class UNotifications * notificationsUser) { notificationsUsers.push_back(notificationsUser); }
    void AddILauncherApi(class ILauncherApi * const launcherApi) { launcherApis.push_back(launcherApi); };
    void SetSignalingApp(class ISignalingApp * signalingApp) { this->signalingApp = signalingApp; }
    void SetHotkeyAction(int type, const char * dialNumber);

    void LauncherApiSetFocus(const char * app, const char * title);

    void RegistrationCheck();
};

ClientConfig * appclientConfig;

Browser::Browser(class IIoMux * iomux, class UAppClientMain * appClientMainUser, class DebugLogFlags * debugLogFlags) :
    STaskContext(iomux),
    appClientConnectingTimer(iomux, this),
    readConfigBase(JSON_ID_NONE),
    readConfigBuffer(""),
    readConfigJson(readConfigBuffer),
    clientConfig(this, this)
{
    this->iomux = iomux;
    this->appClientMainUser = appClientMainUser;
    this->debugLogFlags = debugLogFlags;
    this->client = 0;
    this->appproxy = 0;
    this->mediaClient = NULL;
    this->callKitUser = NULL;
    this->signalingApp = NULL;
    this->signalingConfig = NULL;
    this->externalAppsApi = NULL;
    this->externalAppsStr = NULL;
    url = NULL;
    webserverUrl = NULL;
    webserverPassword = NULL;
    proxy = NULL;
    startFixCertUrl = NULL;
    fixCert = NULL;
    autostartSettingsApp = NULL;
    appClientConnectionUrl = NULL;
    appClientConnected = false;
    registrationCheckRequests = 0;
    write = NULL;
    writeConfig = false;
    writeConfigScheduled = false;
    stopping = false;
    appclientConfig = &clientConfig;

    iomux->RegisterShutdownHandler(this);
    clientConfig.signalingApp = true;
    
    ReadConfig();
    debugLogFlags->Set(NULL, NULL, clientConfig.logFlags.value, clientConfig.noHexdump.value);

#ifdef _IOS_
    appClientMainUser->AppClientMainActiveAfterCloseAsked(clientConfig.activeAfterCloseAsked.value);
#endif
    if (clientConfig.pushPassword.value) appClientMainUser->AppClientMainSetPushPassword(clientConfig.pushPassword.value);
    appClientMainUser->AppClientMainPushNotifications(clientConfig.pushNotifications.value);
}

Browser::~Browser()
{
    if (url) free(url);
    if (webserverUrl) free(webserverUrl);
    if (webserverPassword) free(webserverPassword);
    if (proxy) free(proxy);
    if (startFixCertUrl) free(startFixCertUrl);
    if (fixCert) free(fixCert);
    if (autostartSettingsApp) free(autostartSettingsApp);
    if (appClientConnectionUrl) free(appClientConnectionUrl);
    if (externalAppsStr) free(externalAppsStr);
}

void Browser::SetAppProxy(class IAppProxy * appproxy, const char * proxy)
{
    this->appproxy = appproxy;
    if (this->proxy) free(this->proxy);
    this->proxyLen = strlen(proxy);
    this->proxy = (char *)(malloc(proxyLen + 1));
    memcpy(this->proxy, proxy, this->proxyLen + 1);
};

UWebserverGet * Browser::ClientWebserverGetAccept(const char * resourceName)
{
    size_t len = strlen(_BUILD_STRING_) + 128;
    char * tmp = (char *)alloca(len);
    _snprintf(tmp, len, "/%s/cert.mobileconfig", _BUILD_STRING_);
    if (!str::n_casecmp(resourceName, tmp, strlen (tmp))) {
        return (new class TaskGetCertMobileconfig(client, appClientMainUser, fixCert, fixCertLen));
    }
    if (!str::n_casecmp(resourceName, "/contactsphoto", strlen("/contactsphoto"))) {
        return (new class TaskWebserverGet(client, appClientMainUser, resourceName));
    }
    return NULL;
}

void Browser::ClientConfigOptionsToJson(class json_io & json, word base, char * & tmp)
{
#ifdef __ANDROID__
    word arr = json.add_array(base, "audioApis");
    unsigned i = 0;
    while (clientConfig.audioApi.map[i].name) {
        word api = json.add_object(arr, 0);
        json.add_string(api, "id", clientConfig.audioApi.map[i].name);
        json.add_string(api, "name", clientConfig.audioApi.map[i].name);
        i++;
    }
#endif
}

void Browser::ClientConfigToJson(class json_io & json, word base, char * & tmp)
{
    word baseObject;

    clientConfig.items.ToJson(json, base, tmp);
    baseObject = json.add_object(base, "signalingApp");
    json.add_bool(baseObject, "value", clientConfig.signalingApp);
    json.add_bool(baseObject, "force", false);
    if(!externalApps.empty()) {
        baseObject = json.add_array(base, "externalApps");
        ExternalApp * app = externalApps.front();
        while(app) {
            word e = json.add_object(baseObject, "app");
            json.add_int(e, "id", app->id, tmp);
            json.add_string(e, "name", app->name);
            json.add_string(e, "path", app->path);
            if(app->param) json.add_string(e, "param", app->param);
            json.add_bool(e, "autostart", app->autostart);
            app = app->goNext();
        }
    }
}

void Browser::ClientConfigItemAdded(IClientConfigItem * item)
{
    if (readConfigBase != JSON_ID_NONE) {
        clientConfig.items.SetItemAdminSettings(item, readConfigJson, readConfigBase, false);
    }
}

void Browser::ClientConfigItemSet(IClientConfigItem * item)
{
    WriteConfig();
}

void Browser::Apply(ClientConfigLogFlags * item)
{
    unsigned i;

    ulong64 mask = 0;
    for (i = 0; i < ICLIENT_LOG_FLAG_COUNT; i++) {
        mask |= IClient::LogFlagTable[i];
    }
    item->value = item->value & mask;
    debugLogFlags->Set(NULL, NULL, clientConfig.logFlags.value, clientConfig.noHexdump.value);
}

void Browser::Apply(ClientConfigNoHexdump * item)
{
    debugLogFlags->Set(NULL, NULL, clientConfig.logFlags.value, clientConfig.noHexdump.value);
}

void Browser::Apply(ClientConfigOrientationAdaptationOff * item)
{
    appClientMainUser->AppClientMainOrientationAdaptationOff(item->value);
}

void Browser::Apply(ClientConfigDisplayOnIfCharging * item)
{
    appClientMainUser->AppClientMainDisplayOnIfCharging(item->value);
}

void Browser::Apply(ClientConfigNoPushAutostart * item)
{
}

void Browser::Apply(ClientConfigAudioApi * item)
{
}

void Browser::Apply(ClientConfigBluetoothHeadsetMode * item)
{
#ifdef __ANDROID__
    appClientMainUser->AppClientMainBluetoothHeadsetMode(item->value);
#endif
}

void Browser::Apply(ClientConfigPhoneAccountEnabled * item)
{
}

void Browser::Apply(ClientConfigRingInHeadset * item)
{
#ifdef _IOS_
    appClientMainUser->AppClientMainRingInHeadset(item->value);
#endif
}

void Browser::Apply(ClientConfigActiveAfterClose * item)
{
#ifdef _IOS_
    appClientMainUser->AppClientMainActiveAfterClose(item->value);
#endif
}

void Browser::Apply(ClientConfigActiveAfterCloseAsked * item)
{
}

void Browser::Apply(ClientConfigAddressBookInaccessibleWarned * item)
{
}

void Browser::Apply(ClientConfigPushNotifications * item)
{
}

void Browser::Apply(ClientConfigPushPassword * item)
{
}

void Browser::Apply(ClientConfigDownloadedBuild * item)
{
}

void Browser::Apply(ClientConfigNoVpnAddresses * item)
{
    if (this->mediaClient) {
        this->mediaClient->SetNoVpnAddresses(item->value);
    }
    if (this->signalingConfig) {
        this->signalingConfig->SetNoVpnAddresses(item->value);
    }
}

void Browser::Apply(ClientConfigShowDesktopNotifications * item)
{
    appClientMainUser->AppClientMainSetShowDesktopNotifications(item->value);
}

void Browser::Apply(ClientConfigAutostart * item)
{
    appClientMainUser->AppClientMainSetAutostart(item->value);
}

void Browser::Apply(ClientConfigDocking * item)
{
    appClientMainUser->AppClientMainSetDocking(item->value);
}

void Browser::Apply(ClientConfigAutoOffline * item)
{
    appClientMainUser->AppClientMainSetOffline(item->value);
}

void Browser::Apply(ClientConfigHotkeyDial * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_DIAL, item->value);
}

void Browser::Apply(ClientConfigHotkeyAccept * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_ACCEPT, item->value);
}

void Browser::Apply(ClientConfigHotkeyReject * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_REJECT, item->value);
}

void Browser::Apply(ClientConfigHotkeyAppLink0 * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_APPLINK0, item->value);
}

void Browser::Apply(ClientConfigHotkeyAppLink1 * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_APPLINK1, item->value);
}

void Browser::Apply(ClientConfigHotkeyAppLink2 * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_APPLINK2, item->value);
}

void Browser::Apply(ClientConfigRecordingUrl * item)
{
    if (this->mediaClient) {
        this->mediaClient->SetRecordingUrl(item->value, item->force);
    }
    if (this->signalingConfig) {
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::Apply(ClientConfigIsRecordingByDefaultOn * item)
{
    if (this->signalingConfig) {
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::Apply(ClientConfigRecordExternalOnly * item)
{
    if (this->signalingConfig) {
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::Apply(ClientConfigAvoidNativeCallScreen * item)
{
    appClientMainUser->AppClientMainAvoidNativeCallScreen(item->value);
}

void Browser::Apply(ClientConfigHideFromNativeCallList * item)
{
    appClientMainUser->AppClientMainHideFromNativeCallList(item->value);
}

void Browser::Apply(ClientConfigCurrentUserSip * item)
{
}

void Browser::Apply(ClientConfigLocalStorage * item)
{
}

void Browser::SetAdminSettings(class json_io & json, word base, bool pbx, void * context)
{
    word baseStart;
    bool present;

    clientConfig.items.SetAdminSettings(json, base, pbx);
    baseStart = json.get_object(base, "signalingApp");
    if (baseStart != JSON_ID_ROOT) {
        clientConfig.signalingApp = json.get_bool(baseStart, "value", &present);
    }
}

void Browser::ClientJsonToConfig(class json_io & json, word base, void * context)
{
    bool present;
    int extAppId = json.get_int(base, "externalAppId", &present);
    const char * extAppName = json.get_string(base, "externalAppName");
    const char * extAppPath = json.get_string(base, "externalAppPath");
    if(present) {
        if(extAppName && extAppPath) SetExternalApp(extAppId, extAppName, extAppPath, json.get_string(base, "externalAppParam"), json.get_bool(base, "externalAutostartApp"));
        else DelExternalApp(extAppId);
    }
    word baseSettings = json.get_object(base, "adminSettings");
    SetAdminSettings(json, (baseSettings != JSON_ID_ROOT) ? baseSettings : base, false, context);
    WriteConfig();
}

void Browser::ClientGetLogActive(bool active)
{
    debugLogFlags->GetLogActive(active);
}

void Browser::DelExternalApp(int id)
{
    this->Log(LOG_BROWSER, "Browser(%p)::DelExternalApp id=(%d)", this, id);
    if(!externalApps.empty()) {
        class ExternalApp * a = externalApps.front();
        while(a) {
            if(a->id == id) {
                a->remove();
                delete a;
                break;
            }
            a = a->goNext();
        }
    }
    UpdateExternalApps();
}

void Browser::SetExternalApp(int id, const char * name, const char * path, const char * param, bool autostart)
{
    this->Log(LOG_BROWSER, "Browser(%p)::SetExternalApp id=(%d) app=(%s) path=(%s) param=(%s) autostart=%d", this, id, name, path, param, autostart);
    class ExternalApp * a = NULL;
    if(!externalApps.empty()) {
        a = externalApps.front();
        while(a) {
            if(a->id == id) {
                a->Update(name, path, param, autostart);
                break;
            }
            a = a->goNext();
        }
    }
    if(!a) externalApps.push_back(new class ExternalApp(id, name, path, param, autostart));
    UpdateExternalApps();
}

void Browser::UpdateExternalApps()
{
    int jsonSize = 128;
    int intSize = 0;
    ExternalApp * app = externalApps.front();
    while(app) {
        jsonSize += strlen(app->name) + strlen(app->path) + (app->param ? strlen(app->param) : 0) + 64;
        intSize += 32;
        app = app->goNext();
    }
    char b[32];
    char * tmp = b;
    if(externalAppsStr) free(externalAppsStr);
    externalAppsStr = (char *)malloc(jsonSize);
    class json_io jsonApps(externalAppsStr);
    word base = jsonApps.add_object(0xffff, 0);
    word a = jsonApps.add_array(base, "externalApps");
    app = externalApps.front();
    while(app) {
        word e = jsonApps.add_object(a, "app");
        jsonApps.add_int(e, "id", app->id, tmp);
        jsonApps.add_string(e, "name", app->name);
        jsonApps.add_string(e, "path", app->path);
        if(app->param) jsonApps.add_string(e, "param", app->param);
        jsonApps.add_bool(e, "autostart", app->autostart);
        app = app->goNext();
    }
    jsonApps.encode();
    debug->printf("Browser(%p)::SetExternalApp Apps json sizes=%d,%d (%s)", this, jsonSize, intSize, externalAppsStr);
    if(externalAppsApi) externalAppsApi->UpdateExternalApps(externalAppsStr);
}

void Browser::SetApiClientParameters(class ISocketProvider * localSocketProvider, class ISocketProvider * tcp, class ISocketProvider * tls, class IDns * dns, const char * apiRelayUrl, const char * servicePassword, const char * apiRelayPath)
{
    this->externalAppsApi = new class ExternalApps(this, iomux, this, localSocketProvider, servicePassword, "ExternalApps", apiRelayPath);
    if(this->externalAppsStr) this->externalAppsApi->UpdateExternalApps(this->externalAppsStr);
}

void Browser::ExternalAppsApiClientCloseComplete()
{
    debug->printf("Browser(%p)::ExternalAppsApiClientCloseComplete", this);

    delete externalAppsApi;
    externalAppsApi = NULL;
    if (stopping) {
        TryStop();
    }
}

class Webserver * webserver;
char fixCertUrl[512];

void Browser::ClientFixCert()
{
    webserver->SetServerCertificate(webserverPassword, fixCert, fixCertLen, NULL);
    ISSLProvider::SharedInstance()->SetDefaultClientCertificate(webserver, fixCert, fixCertLen);
    char * certString = (char *)(malloc(((fixCertLen + 2) / 3) * 4 + 1));
    encode_base64(fixCert, certString, fixCertLen);
    appClientMainUser->AppClientMainCertificate(certString);
    free(certString);
    appClientMainUser->AppClientMainFixCert(fixCertUrl, fixCert, fixCertLen);
}

bool Browser::ClientEMailLogAvailable()
{
    return true;
}

void Browser::ClientEMailLog()
{
    appClientMainUser->AppClientMainEMailLog();
}

void Browser::ClientClearLog()
{
    appClientMainUser->AppClientMainClearLog();
}

void Browser::ClientAutostartSettingsApp(char * name, size_t name_len)
{
    str::to_str(autostartSettingsApp, name, name_len);
}

void Browser::ClientStartAutostartSettingsApp(const char *name)
{
    appClientMainUser->AppClientMainStartAutostartSettingsApp(name);
}

void Browser::SignalingRegistrationState(bool up)
{
    appClientMainUser->AppClientMainRegistrationState(up);
}

void Browser::SignalingCallsActive(bool active)
{
    appClientMainUser->AppClientMainShowIfLocked("", active);
}

void Browser::TryStop()
{
    if (stopping && !externalAppsApi && !write && !writeConfigScheduled) {
        iomux->ShutdownComplete(this);
    }
}

void Browser::Shutdown()
{
    stopping = true;
    if (externalAppsApi) externalAppsApi->TryClose();
    TryStop();
}

void Browser::ShutdownTimeout()
{
    debug->printf("Browser::ShutdownTimeout %d %d %d", (externalAppsApi != NULL), (write != NULL), writeConfigScheduled);
}

void Browser::ReadConfig()
{
    char configFilePath[2048];
    
    _sprintf(configFilePath, "%s%c%s", get_files_dir(), get_separator_char(), CONFIG_FILE_NAME);
    
    debug->printf("Browser::ReadConfig %s", configFilePath);
    
    IFileRead * read = FileSystem::OpenReadFile(configFilePath, NULL);
    if (read && read->IsOpen()) {
        size_t l = read->Read(readConfigBuffer, MAX_CONFIG_LENGTH - 1);
        readConfigBuffer[l] = '\0';
        char * tmpExtAppsStr = strstr(readConfigBuffer, "\"externalApps");
        if(tmpExtAppsStr) {
            char * tmpExtAppsStrEnd = strstr(tmpExtAppsStr, "]");
            if(tmpExtAppsStrEnd) {
                if(this->externalAppsStr) free(this->externalAppsStr);
                this->externalAppsStr = (char *)malloc(1+(tmpExtAppsStrEnd-tmpExtAppsStr)+2+1);
                strcpy(this->externalAppsStr, "{");
                memcpy(&this->externalAppsStr[1], tmpExtAppsStr, (tmpExtAppsStrEnd-tmpExtAppsStr)+1);
                strcpy(&this->externalAppsStr[1+(tmpExtAppsStrEnd-tmpExtAppsStr)+1], "}");
                if(externalAppsApi) externalAppsApi->UpdateExternalApps(this->externalAppsStr);
            }
        }
        if (readConfigJson.decode()) {
            word last = 0, t, end = 0xFFFF;
            readConfigBase = readConfigJson.get_object(JSON_ID_ROOT, 0);
            word appsArray = readConfigJson.get_array(readConfigBase, "externalApps");
            if(appsArray != end) {
                while((t = readConfigJson.get_object(appsArray, last)) != end) {
                    debug->printf("app=%s t=%d", readConfigJson.get_string(t, "name"), t);
                    if(readConfigJson.get_string(t, "name") && readConfigJson.get_string(t, "path")) {
                        bool present;
                        int appId = readConfigJson.get_int(t, "id", &present);
                        const char * appName = readConfigJson.get_string(t, "name");
                        class ExternalApp * app = new class ExternalApp(appId, appName, readConfigJson.get_string(t, "path"), readConfigJson.get_string(t, "param"), readConfigJson.get_bool(t, "autostart"));
                        this->externalApps.push_back(app);
                    }
                }
            }
            SetAdminSettings(readConfigJson, readConfigBase, false, NULL);
        }
        read->Close();
    }
}

void Browser::TaskComplete(class ITask * const task)
{
    if (task == write) {
        delete write;
        write = 0;
        copy_to_external(CONFIG_FILE_NAME);
        if (writeConfig) {
            writeConfig = false;
            WriteConfig();
        }
        if (stopping && !write) {
            TryStop();
        }
    }
}

void Browser::TaskFailed(class ITask * const task)
{
    if (task == write) {
        delete write;
        write = 0;
        if (writeConfig) {
            writeConfig = false;
            WriteConfig();
        }
        if (stopping && !write) {
            TryStop();
        }
    }
}

void Browser::TaskProgress(class ITask * const task, dword progress)
{
    if (task == write) {
        char temp[1024];
        char * tmp = temp;
        char buffer[MAX_CONFIG_LENGTH];
        class json_io json(buffer);
        word base = json.add_object(0xffff, 0);
        ClientConfigToJson(json, base, tmp);
        json.encode();
        write->write((byte *)buffer, strlen(buffer), true);
        writeConfig = false;
    }
}

void Browser::WriteConfig()
{
    if (write) {
        writeConfig = true;
    }
    else if (!writeConfigScheduled && !stopping) {
        writeConfigScheduled = true;
        SetExec(iomux, this, (AppClientWriteConfig *) NULL);
    }
}

void Browser::IoExec(AppClientWriteConfig * context)
{
    char configFilePath[2048];
    _sprintf(configFilePath, "%s%c", get_files_dir(), get_separator_char());
    write = new TaskFileWrite(this, configFilePath, CONFIG_FILE_NAME, false);
    write->Start(this);
    if (client) {
        client->ClientConfigChanged(this, NULL);
    }
    writeConfigScheduled = false;
}

void Browser::SetUrl(const char *url, const char *webserverUrl, const char *webserverPassword, const char *startFixCertUrl, const byte *fixCert, size_t fixCertLen)
{
    if (this->url) free(this->url);
    this->url = _strdup(url);
    if (this->webserverUrl) free(this->webserverUrl);
    this->webserverUrl = _strdup(webserverUrl);
    if (this->webserverPassword) free(this->webserverPassword);
    this->webserverPassword = _strdup(webserverPassword);
    if (this->startFixCertUrl) free(this->startFixCertUrl);
    this->startFixCertUrl = _strdup(startFixCertUrl);
    if (this->fixCert) free(this->fixCert);
    this->fixCert = (byte *)(malloc(fixCertLen));
    memcpy(this->fixCert, fixCert, fixCertLen);
    this->fixCertLen = fixCertLen;
}

void Browser::AddISignalingConfig(class ISignalingConfig* const signalingConfig)
{
    this->signalingConfig = signalingConfig;
    if (this->signalingConfig) {
        this->signalingConfig->SetNoVpnAddresses(clientConfig.noVpnAddresses.value);
        this->signalingConfig->SetCurrentUserSip(clientConfig.currentUserSip.value);
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::AddIAppClientMedia(class IAppClientMedia * const mediaClient)
{
    this->mediaClient = mediaClient;
    if (this->mediaClient) {
        this->mediaClient->SetNoVpnAddresses(clientConfig.noVpnAddresses.value);
        this->mediaClient->SetCurrentUserSip(clientConfig.currentUserSip.value);
        this->mediaClient->SetRecordingUrl(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force);
    }
}

void Browser::SetAutostartSettingsApp(const char *name)
{
    if (autostartSettingsApp) free(autostartSettingsApp);
    autostartSettingsApp = _strdup(name);
}

void Browser::SetActiveAfterClose(bool active)
{
#ifdef _IOS_
    clientConfig.activeAfterClose.Set(active);
#endif
}

void Browser::SetActiveAfterCloseAsked(bool asked)
{
#ifdef _IOS_
    clientConfig.activeAfterCloseAsked.Set(asked);
#endif
}

void Browser::SetAddressBookInaccessibleWarned(bool warned)
{
#ifdef _IOS_
    clientConfig.addressBookInaccessibleWarned.Set(warned);
#endif
}

void Browser::PushNotification()
{
    if ((clientConfig.pushNotifications.value == PUSH_NOTIFICATIONS_AUTO) || (clientConfig.pushNotifications.value == PUSH_NOTIFICATIONS_ON)) {
        clientConfig.pushNotifications.Set(PUSH_NOTIFICATIONS_ACTIVE);
        appClientMainUser->AppClientMainPushNotifications(clientConfig.pushNotifications.value);
    }
}

void Browser::SetPhoneAccountEnabled(bool enabled)
{
#ifdef __ANDROID__
    clientConfig.phoneAccountEnabled.Set(enabled);
#endif
}

void Browser::SetLocalStorageItem(const char * id, const char * val)
{
    clientConfig.localStorage.SetItem(id, val);
}

char * Browser::GetLocalStorageItem(const char * id)
{
    return clientConfig.localStorage.GetItem(id);
}

void Browser::SetHotkeyAction(int type, const char * dialNumber)
{
    if ((type == HOTKEY_APPLINK0) || (type == HOTKEY_APPLINK1) || (type == HOTKEY_APPLINK2)) {
        for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
            (*i)->OpenPermalink((type == HOTKEY_APPLINK2) ? clientConfig.hotkeyAppLink2.appLink : ((type == HOTKEY_APPLINK1) ? clientConfig.hotkeyAppLink1.appLink : clientConfig.hotkeyAppLink0.appLink));
        }
    }
    else {
        for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
            (*i)->SetHotkeyAction(true, type, dialNumber);
        }
    }
}

void Browser::AppProxyReady()
{
    debug->printf("client ready");
    appClientMainUser->AppClientMainListening(url, webserverUrl, startFixCertUrl);
}

void Browser::AppProxyConnectionMonitor(const char *url, bool up)
{
    if (appClientConnectionUrl && (strcmp(url, appClientConnectionUrl) == 0)) {
        appClientConnected = up;
        while (registrationCheckRequests != 0) {
            appClientMainUser->AppClientMainRegistrationCheckConfirm();
            registrationCheckRequests--;
        }
        appClientConnectingTimer.Start(2000);
    }
}

void Browser::TimerOnTimeout(AppClientConnectingTimer * timer)
{
}

void Browser::RegistrationCheck()
{
    registrationCheckRequests++;
    if (!appClientConnectionUrl || appClientConnected || appClientConnectingTimer.IsRunning()) {
        while (registrationCheckRequests != 0) {
            appClientMainUser->AppClientMainRegistrationCheckConfirm();
            registrationCheckRequests--;
        }
    }
}

void Browser::LauncherApiClosing(class ILauncherApi * launcherApi)
{
    launcherApis.remove(launcherApi);
}

void Browser::LauncherApiDisablePowerSaving(bool active)
{
    appClientMainUser->AppClientMainDisablePowerSaving(active);
}

void Browser::LauncherApiSetFocus(const char * app, const char * title)
{
    appClientMainUser->AppClientMainSetFocus(app, title);
}

void Browser::LauncherApiAppWindowOpened(const char * app, const char * title)
{
    appClientMainUser->AppClientMainAppWindowOpened(app, title);
}

void Browser::LauncherApiSetResize(const char * app, int width, int height)
{
    appClientMainUser->AppClientMainSetResize(app, width, height);
}

void Browser::LauncherApiSetMaximized(const char * app, bool on)
{
}

void Browser::LauncherApiRegistrationState(bool up)
{
    appClientMainUser->AppClientMainRegistrationState(up);
}

void Browser::LauncherApiDialGsm(const char * number)
{
    appClientMainUser->AppClientMainDialGSM(number);
}

void Browser::LauncherApiShowIfLocked(const char * ref, bool show)
{
    appClientMainUser->AppClientMainShowIfLocked(ref, show);
}

void Browser::LauncherApiShowUnlock()
{
    appClientMainUser->AppClientMainShowUnlock();
}

void Browser::AddLocalNotification(const char * ref, LocalNotificationType type, const char * title, const char * text, const char * smallIcon, const char * largeIcon,
    const char * soundUrl, const char * buttonTitle, const char * acceptTitle, const char * rejectTitle, const char * replyTitle, const char * link, UNotifications * user)
{
    NotificationsUsers::iterator i = notificationsUsers.begin();
    while ((i != notificationsUsers.end()) && ((*i) != user)) {
        i++;
    }
    if (i == notificationsUsers.end()) {
        notificationsUsers.push_back(user);
    }
    appClientMainUser->AppClientMainAddLocalNotification(ref, type, title, text, smallIcon, largeIcon, soundUrl, buttonTitle, acceptTitle, rejectTitle, link);
}

void Browser::RemoveLocalNotification(const char * ref)
{
    appClientMainUser->AppClientMainRemoveLocalNotification(ref);
}

void Browser::LocalNotificationClicked(const char* ref, enum LocalNotificationAction action, const char* replyText)
{
    for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
        (*i)->LauncherApiLocalNotificationClicked(ref, action);
    }
}

void Browser::LauncherApiSetBadgeCount(const char * ref, unsigned count, const char * text)
{
    appClientMainUser->AppClientMainSetBadgeCount(ref, count, text);
}

void Browser::LauncherApiStatusBarAppearance(bool hidden, bool blackText, dword color)
{
    appClientMainUser->AppClientMainStatusBarAppearance(hidden, blackText, color);
}

void Browser::LauncherApiCacheable(const char * url, const char * filename)
{
    appproxy->Cacheable(url, filename);
}

void Browser::LauncherApiSetPushPassword(const char * pwd)
{
    clientConfig.pushPassword.Set(pwd);
    appClientMainUser->AppClientMainSetPushPassword(pwd);
}

void Browser::LauncherApiSetDownloadedBuild(const char * build)
{
    clientConfig.downloadedBuild.Set(build);
}

void Browser::LauncherApiSoftwareUpdate()
{
    appClientMainUser->AppClientMainSoftwareUpdate();
}

void Browser::LauncherApiNoPushAutostart(bool noPushAutostart)
{
    clientConfig.noPushAutostart.Set(noPushAutostart);
}

int phoneLanguageId = MYAPPS_LANGUAGE_EN;

void Browser::LauncherApiSetLangId(const char* langId)
{
    this->Log(LOG_BROWSER, "Browser(%p)::LauncherApiSetLangId lang=%s", this, langId ? langId : "");
    phoneLanguageId = Language::GetLanguageId(langId, myappsLanguageTable, MYAPPS_NUM_LANGUAGES, MYAPPS_LANGUAGE_EN);
    appClientMainUser->AppClientMainSetLanguageId(phoneLanguageId);
    if (this->mediaClient) this->mediaClient->SetLangId(langId);
}

void Browser::LauncherApiSetAdminSettings(class json_io& model, word baseSettings)
{
    this->Log(LOG_BROWSER, "Browser(%p)::LauncherApiSetAdminSettings", this);
    SetAdminSettings(model, baseSettings, true, NULL);
    WriteConfig();
}

void Browser::LauncherApiSetUser(const char * sip)
{
    this->Log(LOG_BROWSER, "Browser(%p)::LauncherApiSetUser sip (%s)", this, sip ? sip : "");
    clientConfig.currentUserSip.Set(sip);
    if (this->mediaClient) {
        this->mediaClient->SetCurrentUserSip(clientConfig.currentUserSip.value);
    }
    if (this->signalingConfig) {
        this->signalingConfig->SetCurrentUserSip(clientConfig.currentUserSip.value);
    }
}

void Browser::LauncherApiAppClientUrl(const char * url)
{
    unsigned i, j;
    appClientConnected = false;
    if (appClientConnectionUrl) free(appClientConnectionUrl);
    if (url) {
        i = strlen(url);
        while ((i != 0) && (url[i-1] != '/')) {
            i--;
        }
        if ((i > proxyLen) && (memcmp(url, proxy, proxyLen) == 0)) {
            appClientConnectionUrl = (char *)(malloc(i + (i - proxyLen + 6 + sizeof("websocket"))));
            j = 0;
            appClientConnectionUrl[j++] = 'w';
            appClientConnectionUrl[j++] = 's';
            if (proxy[4] == 's') appClientConnectionUrl[j++] = 's';
            appClientConnectionUrl[j++] = ':';
            appClientConnectionUrl[j++] = '/';
            appClientConnectionUrl[j++] = '/';
            memcpy(&(appClientConnectionUrl[j]), &(url[proxyLen]), i - proxyLen);
            memcpy(&(appClientConnectionUrl[j + i - proxyLen]), "websocket", sizeof("websocket"));
            appproxy->ConnectionMonitor(true);
        }
        else {
            appClientConnectionUrl = NULL;
            appproxy->ConnectionMonitor(false);
        }
    }
    else {
        appClientConnectionUrl = NULL;
        appproxy->ConnectionMonitor(false);
    }
}

void Browser::LauncherApiRccAppConnected(bool up)
{
    appClientMainUser->AppClientMainRccAppConnected(up);
}

void Browser::LauncherApiSetConnInfo(bool up, bool standby, const char * cur, const char * pri, const char * sec, const char * master)
{
    appClientMainUser->AppClientMainSetConnInfo(up, standby, cur, pri, sec, master);
}

void Browser::SetClose(bool closeSupported)
{
    for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
        (*i)->SetClose(closeSupported);
    }
}

void Browser::SetDocking(bool docked)
{
    for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
        (*i)->SetDocking(true, docked);
    }
}

void Browser::CxUse(bool use, const char *ringtone, bool supportsVideo, int maximumCallGroups, int maximumCallsPerCallGroup)
{
    appClientMainUser->AppClientMainCxUse(use, ringtone, supportsVideo, maximumCallGroups, maximumCallsPerCallGroup);
}

void Browser::CxReportNewIncomingCall(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
    bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping)
{
    appClientMainUser->AppClientMainCxReportNewIncomingCall(uuid, number, name, transfer, called,
        hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);
}

void Browser::CxReportOutgoingCallConnecting(const char *uuid)
{
    appClientMainUser->AppClientMainCxReportOutgoingCallConnecting(uuid);
}

void Browser::CxReportOutgoingCallConnected(const char *uuid)
{
    appClientMainUser->AppClientMainCxReportOutgoingCallConnected(uuid);
}

void Browser::CxReportCallUpdated(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
    bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping)
{
    appClientMainUser->AppClientMainCxReportCallUpdated(uuid, number, name, transfer, called,
        hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);
}

void Browser::CxReportCallEnded(const char *uuid, CxCallEndedReason reason)
{
    appClientMainUser->AppClientMainCxReportCallEnded(uuid, reason);
}

void Browser::CxRequestStartCall(const char *uuid, const char *number, const char *name, bool video)
{
    appClientMainUser->AppClientMainCxRequestStartCall(uuid, number, name, video);
}

void Browser::CxRequestEndCall(const char *uuid)
{
    appClientMainUser->AppClientMainCxRequestEndCall(uuid);
}

void Browser::CxRequestSetHeldCall(const char *uuid, bool held)
{
    appClientMainUser->AppClientMainCxRequestSetHeldCall(uuid, held);
}

void Browser::CxRequestSetMutedCall(const char *uuid, bool muted)
{
    appClientMainUser->AppClientMainCxRequestSetMutedCall(uuid, muted);
}

void Browser::CxRequestSetGroupCall(const char *uuid, const char *with)
{
    appClientMainUser->AppClientMainCxRequestSetGroupCall(uuid, with);
}

void Browser::CxRequestPlayDTMF(const char *uuid, const char *digits)
{
    appClientMainUser->AppClientMainCxRequestPlayDTMF(uuid, digits);
}

void Browser::LauncherApiClose()
{
    appClientMainUser->AppClientMainShutdown();
}

/*---------------------------------------------------------------------------*/

#define MEDIA_CERT_FILE_TEMPLATE "cert"
#define MEDIA_CERT_FILE_EXT      ".pem"
#define MAX_CERT_LENGTH          8192

int IProcess::System(class IInstanceLog * const log, const char * command, ...) { return 0; }

bool IEthernet::GetMACAddress(byte * mac, class IInstanceLog * const log)
{
    mac[0] = 0x02;
    mac[1] = 0x00;
    mac[2] = 0x00;
    mac[3] = 0x00;
    mac[4] = 0x00;
    mac[5] = 0x00;
    return true;
}

static char * EncodePassword(const char * webserverPassword)
{
    char * obfuscated = (char *)malloc((strlen(webserverPassword) * 2) + 1);
    char * tmpOb = (char *)obfuscated;
    char * tmp = (char *)webserverPassword;
    int i = 0;
    while (*tmp) {      // duplicate some chars
        *tmpOb = *tmp;
        if (i == 1 || i == 3 || i == 4 || i == 6 || i == 8 || i == 9 || i == 12) {
            ++tmpOb;       // duplicate char
            *tmpOb = *tmp;
        }
        ++i;
        ++tmp;
        ++tmpOb;
    }
    *tmpOb = '\0';
    tmpOb = obfuscated;
    i = 0;
    while (*tmpOb) {    // now increment or decrement some chars
        if (i == 0 || i == 3 || i == 4 || i == 7 || i == 16) {
            *tmpOb = *tmpOb + 1;
        }
        else if (i == 1 || i == 5 || i == 10 || i == 11 || i == 15) {
            *tmpOb = *tmpOb - 1;
        }
        ++i;
        ++tmpOb;
    }
    char * tmpPassword = (char *)malloc(strlen(obfuscated) * 2 + 1);
    encode_base64((const byte *)obfuscated, tmpPassword, strlen(obfuscated));
    free(obfuscated);
    return tmpPassword;
}

static void SetArgs(app_arguments_t * args, AppServiceArgs * service, AppInstanceArgs * instance, const char * name, const char * domain, const char * password, const char * webserverPath, const char * workingPath)
{
    if (service) {
        service->serviceID = name;
        service->manager = NULL;
        service->logFlags = args->logFlags;
        service->segfaulted = args->segfaulted;
    }
    if (instance) {
        instance->appName = name;
        instance->appDomain = domain;
        instance->appPassword = password;
        instance->webserver = args->webserverLocalPath;
        instance->webserverPath = webserverPath;
        instance->workingPath = workingPath;
        instance->logFlags = args->logFlags;
    }
}

class UAppClientMain * appClientMainUser;
ISocketProvider * clientSocketProviderUDP;
ISocketProvider * clientSocketProviderTCP;
ISocketProvider * clientSocketProviderTLS;
ISocketProvider * clientSocketProviderLocal;
class AppClientMain * appClientMain;
class Browser * browser;
SignalingAppWebsocketClientProvider *appWebsocketClientProvider;
SignalingApp * signalingApp;

void phone_set_push_token(const char *token)
{
    debug->printf("phone_set_push_token %s", token);
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetPushToken(token);
    }
}

void phone_push_notification()
{
    debug->printf("phone_push_notification");
    browser->PushNotification();
}

void phone_set_expect_touches(bool expectTouchesSupported, bool expectTouches)
{
    debug->printf("phone_set_expect_touches %u %u", expectTouchesSupported, expectTouches);
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetExpectTouches(expectTouchesSupported, expectTouches);
    }
}

void phone_set_shown_locked(bool shownLockedSupported, bool shownLocked)
{
    debug->printf("phone_set_shown_locked %u %u", shownLockedSupported, shownLocked);
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetShownLocked(shownLockedSupported, shownLocked);
    }
}

void phone_set_proximity(bool proximitySupported, bool proximity)
{
    debug->printf("phone_set_proximity %u %u", proximitySupported, proximity);
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetProximity(proximitySupported, proximity);
    }
}

void phone_local_notification_clicked(const char *ref)
{
    debug->printf("phone_local_notification_clicked %s", ref);
    for (NotificationsUsers::iterator i = browser->notificationsUsers.begin(); i != browser->notificationsUsers.end(); i++) {
        (*i)->LocalNotificationClicked(ref, LOCAL_NOTIFICATION_ACTION_DEFAULT);
    }
}

void phone_local_notification_accept(const char *ref)
{
    debug->printf("phone_local_notification_accept %s", ref);
    for (NotificationsUsers::iterator i = browser->notificationsUsers.begin(); i != browser->notificationsUsers.end(); i++) {
        (*i)->LocalNotificationClicked(ref, LOCAL_NOTIFICATION_ACTION_ACCEPT);
    }
}

void phone_local_notification_reject(const char *ref)
{
    debug->printf("phone_local_notification_reject %s", ref);
    for (NotificationsUsers::iterator i = browser->notificationsUsers.begin(); i != browser->notificationsUsers.end(); i++) {
        (*i)->LocalNotificationClicked(ref, LOCAL_NOTIFICATION_ACTION_REJECT);
    }
}

void phone_local_notification_reply(const char *ref, const char *replyText)
{
    debug->printf("phone_local_notification_reply %s", ref);
    for (NotificationsUsers::iterator i = browser->notificationsUsers.begin(); i != browser->notificationsUsers.end(); i++) {
        (*i)->LocalNotificationClicked(ref, LOCAL_NOTIFICATION_ACTION_REPLY, replyText);
    }
}

static unsigned NormalizeE164(const char *number, char * buffer, unsigned length)
{
    const char * n;
    unsigned i = 0;

    if (number) {
        n = number;;
        while (*n != '\0') {
            if ((*n >= 0) && (*n != ' ') && (*n != '(') && (*n != ')') && (*n != '-') && (*n != '/') && (*n != '.') && (*n != ':') && (*n != ';') && (*n != '\r') && (*n != '\n')) {
                if (i+1 < length) {
                    buffer[i] = *n;
                    i++;
                }
            }
            n++;
        }
    }
    buffer[i] = '\0';
    return i;
}

void phone_dial_number(const char *number, const char *name, int video_mode)
{
    char normalizedNumber[1024];
    debug->printf("phone_dial_number");
    if (number) {
        NormalizeE164(number, normalizedNumber, sizeof(normalizedNumber));
    }
    if ((!number || (number[0] == '\0')) && (!name || (name[0] == '\0'))) {
        for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
            (*i)->PrepareCall("");
        }
    }
    else if (!browser->signalingApp || !browser->signalingApp->StartCall(0, number ? normalizedNumber : NULL, name, (StartCallVideoMode) video_mode)) {
        for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
            (*i)->StartCall(0, number, name, (StartCallVideoMode) video_mode);
        }
    }
}

void phone_dial_sip(const char *sip, const char *name, int video_mode)
{
    debug->printf("phone_dial_sip");
    if (!browser->signalingApp || !browser->signalingApp->StartCall(sip, 0, name, (StartCallVideoMode) video_mode)) {
        for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
            (*i)->StartCall(sip, 0, name, (StartCallVideoMode) video_mode);
        }
    }
}

void phone_chat(const char *sip)
{
    debug->printf("phone_chat");
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->StartChat(sip);
    }
}

void phone_external_call_idle(const char *number)
{
    debug->printf("phone_external_call idle");
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetGsmState(true, GSM_CALL_STATE_IDLE);
    }
}

void phone_external_call_ringing(const char *number)
{
    debug->printf("phone_external_call ringing");
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetGsmState(true, GSM_CALL_STATE_RINGING);
    }
}

void phone_external_call_offhook(const char *number)
{
    debug->printf("phone_external_call offhook");
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetGsmState(true, GSM_CALL_STATE_OFFHOOK);
    }
}

void phone_cx_supported(bool supported)
{
    debug->printf("phone_cx_supported %u", supported);
    browser->callKitUser->CxSupported(supported);
}

void phone_cx_start_call_action(const char * uuid, const char * number, const char * name, bool video)
{
    debug->printf("phone_cx_start_call_action");
    browser->callKitUser->CxStartCallAction(uuid, number, name, video);
}

void phone_cx_answer_call_action(const char * uuid)
{
    debug->printf("phone_cx_answer_call_action");
    browser->callKitUser->CxAnswerCallAction(uuid);
}

void phone_cx_end_call_action(const char * uuid, int call_error)
{
    debug->printf("phone_cx_end_call_action %d", call_error);
    browser->callKitUser->CxEndCallAction(uuid, (enum CxCallError) call_error);
}

void phone_cx_set_held_call_action(const char * uuid, bool held)
{
    debug->printf("phone_cx_set_held_call_action %u", held);
    browser->callKitUser->CxSetHeldCallAction(uuid, held);
}

void phone_cx_set_muted_call_action(const char * uuid, bool muted)
{
    debug->printf("phone_cx_set_muted_call_action %u", muted);
    browser->callKitUser->CxSetMutedCallAction(uuid, muted);
}

void phone_cx_audio_route_changed(const char * uuid, const char * deviceId)
{
    debug->printf("phone_cx_audio_route_changed %s", deviceId);
    browser->callKitUser->CxAudioRouteChanged(uuid, deviceId);
}

void phone_cx_set_group_call_action(const char * uuid, const char * with)
{
    debug->printf("phone_cx_set_group_call_action");
    browser->callKitUser->CxSetGroupCallAction(uuid, with);
}

void phone_cx_play_dtmf_call_action(const char * uuid, const char * digits)
{
    debug->printf("phone_cx_play_dtmf_call_action %s", digits);
    browser->callKitUser->CxPlayDTMFCallAction(uuid, digits);
}

void phone_cx_call_error(const char * uuid, int call_error)
{
    debug->printf("phone_cx_call_error %d", call_error);
    browser->callKitUser->CxCallError(uuid, (enum CxCallError) call_error);
}

void phone_cx_show_incoming_call_ui(const char * uuid)
{
    debug->printf("phone_cx_show_incoming_call_ui");
    browser->callKitUser->CxShowIncomingCallUi(uuid);
}

void phone_cx_check_incoming_call(const char * uuid)
{
    debug->printf("phone_cx_check_incoming_call");
    browser->callKitUser->CxCheckIncomingCall(uuid);
}

void phone_wake_phone()
{
    debug->printf("phone_wake_phone");
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->WakePhone();
    }
}

void phone_open_permalink(const char * link)
{
    debug->printf("phone_open_permalink %s", link);
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->OpenPermalink(link);
    }
}

void myApps_set_offline(bool offline)
{
    for (LauncherApis::iterator i = browser->launcherApis.begin(); i != browser->launcherApis.end(); i++) {
        (*i)->SetAutoOffline(true, offline);
    }
}

void myApps_set_hotkey_action(int type, const char * dialNumber)
{
    browser->SetHotkeyAction(type, dialNumber);
}

void phone_active_after_close(bool active)
{
    browser->SetActiveAfterClose(active);
}

void phone_active_after_close_asked(bool asked)
{
    browser->SetActiveAfterCloseAsked(asked);
}

void phone_address_book_inaccessible_warned(bool warned)
{
    browser->SetAddressBookInaccessibleWarned(warned);
}

void phone_set_phone_account_enabled(bool enabled)
{
    browser->SetPhoneAccountEnabled(enabled);
}

void ILocalStorage::SetItem(const char * id, const char * val)
{
    browser->SetLocalStorageItem(id, val);
}

char * ILocalStorage::GetItem(const char * id)
{
    return (browser->GetLocalStorageItem(id));
}

class AppClientMainWebserverBindFailed {};


class AppClientMain : public UDebugLogFlags, public UWebserver,
    public UIoExecSubclass<AppClientMainWebserverBindFailed>
{
    IIoMux * iomux;

    UAppClientMain * user;

    int argc;
    char **argv;

    IDns * dns;
    ISocketProvider * socketProviderTLS;
    ISocketProvider * socketProviderLocal;
    char * encodedPassword;
    app_arguments_t * args;
    const char * domain;
    byte * cert;
    size_t certLen;
    IWebserverPluginProvider * webserverPluginProvider;
    class DebugLogFlags * logFlags;
    class AppClientMedia * appClientMedia;
    class AppClientRinger * appClientRinger;
    class UrlHandler * urlHandler;
    class ILauncherApi * launcherApi;
    class ApiRelayService * apirelayService;
    class AppProxyService * appproxyService;
#if (defined(__ANDROID__) || (defined(_IOS_) && !TARGET_OS_IPHONE))
    class UpdateServiceHandlerService * updateServiceService;
#endif
    class Client * client;

    const char *apirelayLocalPath;
    char *servicePassword;
    bool turnOnly;
    bool iceNoHost;
    bool plainHttp;
    bool noAppProxy;
    bool noUpdateService;
    const char *httpHostname, *httpsCert, *autostartSettingsApp, *pbxServer;
    int appArgc, arg;
    char **appArgv;
    char tPassword[256];
    char webserverUrl[512];
    char startFixCertUrl[512];

    char * webserverPassword;
    word webserverHttpPort;
    word webserverHttpsPort;
    unsigned webserverPortRetries;

    ISignalingService *signalingService;

    void DebugLogFlagsChanged(class DebugLogFlags * debugLogFlags, const char * appName, const char * appDomain, ulong64 flags, bool noHexdump, UDebugLogFlags * src) override {
        appClientMainUser->DebugLogFlagsChanged(debugLogFlags, appName, appDomain, flags, noHexdump, src);
    }

    void SetExec(IIoMux * iomux, UIoExecSubclass<AppClientMainWebserverBindFailed> * ioContext, AppClientMainWebserverBindFailed * execContext) { iomux->SetExec(ioContext, execContext); };
    void IoExec(AppClientMainWebserverBindFailed * context);

public:
    AppClientMain(IIoMux * const iomux, UAppClientMain * const user, int argc, char *argv[]);
    ~AppClientMain();
    void WebserverInitDone(int tcpPort, int tlsPort) override;
    void WebserverInitDone();

    ClientUDPSocketProvider * socketProviderUDP;
    ClientTCPSocketProvider * socketProviderTCP;
    class MediaConfiguration * mediaConfiguration;
    class SignalingMediaConfig * signalingMediaConfig;
};


AppClientMain::AppClientMain(IIoMux * const iomux, UAppClientMain * const user, int argc, char **argv)
{
    int appSharingNumUpdates = 32;
    int appSharingCaptureTimer = 100;
    int appSharingBitrate = 500;
    int appSharingJpegQuality = 65;
    int appSharingUpdateSum = 5000;
    int appSharingWaitMsForAck = 200;
    int mediaDropPacketsTx = 0;
    int mediaDropPacketsRx = 0;
    int execInterval = 160;
    bool phoneLoad = false;
    bool hdVideo = false;
    bool recordRtpStream = false;
    long64 t;
    time_tm_t tm;

    char mediaCertFilePath[2048];
    byte * mediaCert;
    size_t mediaCertLen;

    this->iomux = iomux; 
    this->user = user;
    this->argc = argc;
    this->argv = argv;

    webserver = NULL;
    appClientMainUser = user;
    dns = IDns::Create(iomux);
    clientSocketProviderUDP = socketProviderUDP = ClientCreateUDPSocketProvider();
    clientSocketProviderTCP = socketProviderTCP = ClientCreateTCPSocketProvider();
    clientSocketProviderTLS = socketProviderTLS = CreateTLSSocketProvider(socketProviderTCP);
    clientSocketProviderLocal = socketProviderLocal = ClientCreateLocalSocketProvider();
    IRandom::Init((dword)(ITime::TimeStampMilliseconds() / 1000));

    // start services
    mediaConfiguration = nullptr;
    appWebsocketClientProvider = nullptr;
    this->signalingService = nullptr;
    signalingApp = nullptr;
    signalingMediaConfig = nullptr;

    this->webserverPassword = (char *)malloc(33);
    IRandom::GetChars(this->webserverPassword, 32);
    this->webserverPassword[32] = 0;
    encodedPassword = EncodePassword(this->webserverPassword);
    apirelayLocalPath = NULL;
    servicePassword = NULL;
    turnOnly = false;
    iceNoHost = false;
    plainHttp = true;
    noAppProxy = false;
    noUpdateService = false;
    httpHostname = "localhost";
    httpsCert = NULL;
    autostartSettingsApp = NULL;
    pbxServer = NULL;
    appArgv = (char **)(malloc((argc + 1) * sizeof(char *)));
    appArgc = 0;
    for (arg = 0; arg < argc; arg++) {
        char * tok = strchr(argv[arg], '=');
        if (tok && (strncmp(argv[arg], "--apirelay=", (tok + 1) - argv[arg]) == 0)) {
            apirelayLocalPath = tok + 1;
        }
        else if (tok && (strncmp(argv[arg], "--password=", (tok + 1) - argv[arg]) == 0)) {
            servicePassword = _strdup(tok + 1);
        }
        else if (strcmp(argv[arg], "--no-plain-http") == 0) {
            plainHttp = false;
        }
        else if (strcmp(argv[arg], "--no-appproxy") == 0) {
            noAppProxy = true;
        }
        else if (strcmp(argv[arg], "--no-update-service") == 0) {
            noUpdateService = true;
        }
        else if (strcmp(argv[arg], "--turn-only") == 0) {
            turnOnly = true;
        }
        else if (strcmp(argv[arg], "--ice-no-host") == 0) {
            iceNoHost = true;
        }
        else if (strcmp(argv[arg], "--phoneload") == 0) {
            phoneLoad = true;
        }
        else if (strcmp(argv[arg], "--hd-video") == 0) {
            hdVideo = true;
        }
        else if (strcmp(argv[arg], "--record-rtp-stream") == 0) {
            recordRtpStream = true;
        }
        else if (tok && (strncmp(argv[arg], "--http-hostname=", (tok + 1) - argv[arg]) == 0)) {
            httpHostname = tok + 1;
        }
        else if (tok && (strncmp(argv[arg], "--https-cert=", (tok + 1) - argv[arg]) == 0)) {
            httpsCert = tok + 1;
        }
        else if (tok && (strncmp(argv[arg], "--autostart-settings-app=", (tok + 1) - argv[arg]) == 0)) {
            autostartSettingsApp = tok + 1;
        }
        else if (tok && (strncmp(argv[arg], "--server=", (tok + 1) - argv[arg]) == 0)) {
            pbxServer = tok + 1;
        }
        else if (tok && (strncmp(argv[arg], "--appsharing-num-updates=", (tok + 1) - argv[arg]) == 0)) {
            appSharingNumUpdates = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--appsharing-jpeg-quality=", (tok + 1) - argv[arg]) == 0)) {
            appSharingJpegQuality = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--appsharing-capture-timer=", (tok + 1) - argv[arg]) == 0)) {
            appSharingCaptureTimer = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--appsharing-bitrate=", (tok + 1) - argv[arg]) == 0)) {
            appSharingBitrate = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--appsharing-update-sum=", (tok + 1) - argv[arg]) == 0)) {
            appSharingUpdateSum = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--appsharing-wait-milliseconds-ack=", (tok + 1) - argv[arg]) == 0)) {
            appSharingWaitMsForAck = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--media-drop-packets-tx=", (tok + 1) - argv[arg]) == 0)) {
            mediaDropPacketsTx = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--media-drop-packets-rx=", (tok + 1) - argv[arg]) == 0)) {
            mediaDropPacketsRx = atoi(tok + 1);
        }
        else if (tok && (strncmp(argv[arg], "--exec-interval=", (tok + 1) - argv[arg]) == 0)) {
            execInterval = atoi(tok + 1);
        }
        else {
            appArgv[appArgc++] = argv[arg];
        }
    }
    _sprintf(tPassword, "--app-password=%s", encodedPassword); 
    appArgv[appArgc++] = tPassword;
    args = ParseArguments(appArgc, appArgv);
    domain = "";
    if(!servicePassword) {
        servicePassword = (char *)(malloc(32+1));
        IRandom::GetChars(servicePassword, 32);
        servicePassword[32] = '\0';
    }
    webserverHttpPort = args->httpPort;
    webserverHttpsPort = args->httpsPort;
    webserverPortRetries = 0;

    logFlags = new class DebugLogFlags();
    logFlags->Set(NULL, NULL, args->logFlags, false, this);
    debug->printf("start browser");
    browser = new class Browser(iomux, user, logFlags);
    args->logFlags = logFlags->CommonLogFlags();
    logFlags->Add("browser", domain, browser);

    t = ITime::TimeStampMilliseconds();
    ITime::GetTimeStruct(t, &tm);
    _sprintf(mediaCertFilePath, "%s%c%s%u%s", get_files_dir(), get_separator_char(), MEDIA_CERT_FILE_TEMPLATE, tm.tmYear + 1900, MEDIA_CERT_FILE_EXT);
    IFileRead * read = FileSystem::OpenReadFile(mediaCertFilePath, NULL);
    if (read && read->IsOpen()) {
        mediaCert = (byte *)(malloc(MAX_CERT_LENGTH));
        mediaCertLen = read->Read(mediaCert, MAX_CERT_LENGTH);
        read->Close();
    }
    else {
        mediaCert = ISSLProvider::SharedInstance()->CreateCertificate(browser, &mediaCertLen);
        IFileWrite * write = FileSystem::OpenWriteFile(mediaCertFilePath, false, NULL);
        if (write && write->IsOpen()) {
            write->Write(mediaCert, mediaCertLen);
            write->Close();
        }
    }
    mediaConfiguration = new class MediaConfiguration(turnOnly, iceNoHost, appSharingNumUpdates, appSharingCaptureTimer, appSharingBitrate, appSharingJpegQuality, appSharingUpdateSum, appSharingWaitMsForAck, mediaDropPacketsTx, mediaDropPacketsRx,
        phoneLoad, hdVideo, execInterval, recordRtpStream, mediaCert, mediaCertLen);

    debug->printf("start webserver");
    webserver = new Webserver(iomux, socketProviderTCP, socketProviderTLS, socketProviderLocal, args);
    webserver->SetIpFilter(true);
    if (httpsCert) {
        cert = (byte *)(_strdup(httpsCert));
        certLen = strlen(httpsCert);
    }
    else {
        cert = (byte *)(malloc(mediaCertLen));
        memcpy(cert, mediaCert, mediaCertLen);
        certLen = mediaCertLen;
        char * certString = (char *)(malloc(((certLen + 2) / 3) * 4 + 1));
        encode_base64(cert, certString, certLen);
        user->AppClientMainCertificate(certString);
        free(certString);
    }
    free(mediaCert);
    webserver->SetServerCertificate(webserverPassword, cert, certLen, NULL);
    ISSLProvider::SharedInstance()->SetDefaultClientCertificate(webserver, cert, certLen);
    logFlags->Add("webserver", domain, webserver);

    webserver->Init(this);

    WebserverInitDone();
}



void AppClientMain::IoExec(AppClientMainWebserverBindFailed * context)
{
    delete webserver;
    webserver = NULL;

    webserverPortRetries++;
    if(webserverPortRetries < 100) {
        if ((webserverHttpPort == webserverHttpsPort + 1) || (webserverHttpsPort == webserverHttpPort + 1)) {
            webserverHttpPort += 2;
            webserverHttpsPort += 2;
        }
        else {
            webserverHttpPort++;
            webserverHttpsPort++;
        }
        args->httpPort = webserverHttpPort;
        args->httpsPort = webserverHttpsPort;
        args->appPassword = encodedPassword;
        debug->printf("start webserver again with ports %d,%d", args->httpPort, args->httpsPort);

        webserver = new Webserver(iomux, socketProviderTCP, socketProviderTLS, socketProviderLocal, args);
        webserver->SetIpFilter(true);
        webserver->SetServerCertificate(webserverPassword, cert, certLen, NULL);
        ISSLProvider::SharedInstance()->SetDefaultClientCertificate(webserver, cert, certLen);
        logFlags->Add("webserver", domain, webserver);

        webserver->Init(this);
    }
    else {
        iomux->Shutdown();
    }
}

void AppClientMain::WebserverInitDone(int tcpPort, int tlsPort)
{
}

void AppClientMain::WebserverInitDone()
{
    int tcpPort = webserverHttpPort;
    int tlsPort = webserverHttpsPort;
    int i;
    char url[2048];
    char tmp[512];

    debug->printf("InitWebserver::WebserverInitDone Ports %d,%d", tcpPort, tlsPort);

    if(!tcpPort || !tlsPort) {
        iomux->SetExec(this, (AppClientMainWebserverBindFailed *)0);
        return;
    }

    const char * https = plainHttp ? "" : "s";
    word httpPort = plainHttp ? tcpPort : tlsPort;

    debug->printf("start client");
    webserverPluginProvider = CreateWebserverPluginProvider();
    client = new Client(iomux, webserverPluginProvider, socketProviderLocal, args->webserverLocalPath, "/client", user, browser, servicePassword);
    logFlags->Add("client", domain, client);
    browser->SetClient(client);

    debug->printf("start appproxy");
    char appproxyWorkingPath[PATH_MAX];
    _sprintf(appproxyWorkingPath, "%s%c%s%c", get_files_dir(), get_separator_char(), "appproxy", get_separator_char());
    if (!FileSystem::FileExists(appproxyWorkingPath, 0) && !FileSystem::CreateDirectory(appproxyWorkingPath, 0777, 0)) {
        debug->printf("CreateDirectory failed \"%s\"", appproxyWorkingPath);
    }
    AppServiceArgs appproxyServiceArgs;
    AppInstanceArgs appproxyInstanceArgs;
    SetArgs(args, &appproxyServiceArgs, &appproxyInstanceArgs, "appproxy", domain, servicePassword, "/appproxy", appproxyWorkingPath);
    appproxyService = new AppProxyService(iomux, socketProviderLocal, webserverPluginProvider, socketProviderTCP, socketProviderTLS, &appproxyServiceArgs, browser, plainHttp);
    appproxyService->AppStart(&appproxyInstanceArgs);
    logFlags->Add(appproxyInstanceArgs.appName, appproxyInstanceArgs.appDomain, appproxyService, appproxyService);
    _sprintf(tmp, "http%s://%s:%u%s/%s/", https, httpHostname, httpPort, appproxyInstanceArgs.webserverPath, APPPROXY_REGISTERED_PATH);
    browser->SetAppProxy(appproxyService, tmp);

    debug->printf("start apirelay");
    AppServiceArgs apirelayServiceArgs;
    AppInstanceArgs apirelayInstanceArgs;
    if(apirelayLocalPath == NULL) {
        apirelayLocalPath = _strdup("\\\\.\\pipe\\ApiRelay");
    }
    SetArgs(args, &apirelayServiceArgs, &apirelayInstanceArgs, "apirelay", domain, servicePassword, "/apirelay", 0);
    apirelayService = new ApiRelayService(iomux, socketProviderLocal, webserverPluginProvider, &apirelayServiceArgs, apirelayLocalPath);
    apirelayService->AppStart(&apirelayInstanceArgs);
    logFlags->Add(apirelayInstanceArgs.appName, apirelayInstanceArgs.appDomain, apirelayService, apirelayService);

    debug->printf("start launcherapi");
    const char * urlHandlerLocalAddr = 0;
    urlHandler = new UrlHandler(iomux, socketProviderLocal, urlHandlerLocalAddr);
    logFlags->Add(urlHandler->GetAppName(), urlHandler->GetAppDomain(), urlHandler);
    AppServiceArgs launcherapiServiceArgs;
    AppInstanceArgs launcherapiInstanceArgs;
    SetArgs(args, &launcherapiServiceArgs, &launcherapiInstanceArgs, "launcherapi", domain, servicePassword, 0, 0);
    char apirelayUrl[256];
    _sprintf(apirelayUrl, "ws%s://%s:%u/apirelay/local", https, httpHostname, httpPort);
    launcherapiService = new LauncherApiHandlerService(iomux, 0, socketProviderLocal, socketProviderTCP, socketProviderTLS, dns, &launcherapiServiceArgs, apirelayUrl, servicePassword, _PRODUCT_NAME_, apirelayLocalPath);
    launcherapiService->AppStart(&launcherapiInstanceArgs);
    launcherApi = launcherapiService->launcherApi;
    logFlags->Add(launcherapiInstanceArgs.appName, launcherapiInstanceArgs.appDomain, launcherapiService, launcherapiService);
    urlHandler->SetLauncherApi(launcherapiService->launcherApi);
    launcherApi->SetClose(true);
#if (defined(_IOS_) && !TARGET_OS_IPHONE)
    launcherApi->SetDetach(true);
#endif
    launcherApi->AddLauncherApiUser(browser);
    char configUrl[256];
    _sprintf(configUrl, "http%s://%s:%u/client/config.htm", https, httpHostname, httpPort);
    launcherApi->SetConfigApp(configUrl);
    launcherApi->SetNoPushAutostart(true);
    launcherApi->SetAutoOffline(true, false);
    browser->AddNotificationsUser(browser);
    browser->AddILauncherApi(launcherApi);

    debug->printf("start contactsSearch");
    contactsSearchApiProvider = new ContactsSearchApiProvider(user);

    debug->printf("start contactsLookup");
    contactsLookupApiProvider = new ContactsLookupApiProvider(user);

#if (defined(__ANDROID__) || (defined(_IOS_) && !TARGET_OS_IPHONE))
    if (noUpdateService) {
        debug->printf("start updateService: Do not check for updates");
        updateServiceService = NULL;
    }
    else {
        debug->printf("start updateService");
        char configFilePath[2048];
        _sprintf(configFilePath, "%s%c", get_files_dir(), get_separator_char());
        AppServiceArgs updateServiceServiceArgs;
        AppInstanceArgs updateServiceInstanceArgs;
        SetArgs(args, &updateServiceServiceArgs, &updateServiceInstanceArgs, "updateservicehandler", domain, servicePassword, 0, configFilePath);
        updateServiceService = new UpdateServiceHandlerService(browser, iomux, socketProviderLocal, socketProviderTCP, socketProviderTLS, browser, &updateServiceServiceArgs, apirelayUrl, servicePassword,
#ifdef __ANDROID__
            "myappsandroid", "myapps.apk",
#else
            "myappsmacos", "myapps.pkg",
#endif
            appclientConfig->downloadedBuild.value, apirelayLocalPath, false);
        updateServiceService->AppStart(&updateServiceInstanceArgs);
        logFlags->Add(updateServiceInstanceArgs.appName, updateServiceInstanceArgs.appDomain, updateServiceService, updateServiceService);
    }
#endif
    debug->printf("start medialib");
    AppInstanceArgs medialibInstanceArgs;
    SetArgs(args, 0, &medialibInstanceArgs, "medialib", domain, servicePassword, "/medialib", 0);
    appClientMedia = new class AppClientMedia(iomux, webserverPluginProvider, socketProviderLocal, socketProviderUDP, socketProviderTCP, socketProviderTLS, &medialibInstanceArgs, mediaConfiguration);
    logFlags->Add(medialibInstanceArgs.appName, medialibInstanceArgs.appDomain, appClientMedia);
    browser->AddIAppClientMedia(appClientMedia);

    debug->printf("start ringer");
    AppInstanceArgs ringerInstanceArgs;
    SetArgs(args, 0, &ringerInstanceArgs, "ringer", domain, servicePassword, "/ringer", 0);
    appClientRinger = new class AppClientRinger(iomux, webserverPluginProvider, socketProviderLocal, &ringerInstanceArgs, mediaConfiguration);
    logFlags->Add(ringerInstanceArgs.appName, ringerInstanceArgs.appDomain, appClientRinger);

    AppServiceArgs signalingServiceArgs;
    AppInstanceArgs signalingInstanceArgs;
    if(appclientConfig->signalingApp) {
        debug->printf("start signaling");

        appWebsocketClientProvider = CreateSignalingAppWebsocketClientProvider(iomux, socketProviderTCP, socketProviderTLS, dns);
        signalingMediaConfig = new SignalingMediaConfig(socketProviderUDP, socketProviderTCP, socketProviderTLS, appClientMedia->GetSocketContext(), 50000, 50099, 50100, 50199, nullptr, nullptr, nullptr, nullptr, MediaType::AUDIO, false, turnOnly, iceNoHost);

        SetArgs(args, &signalingServiceArgs, &signalingInstanceArgs, "signaling", domain, servicePassword, "/signaling", 0);

        signalingService = ISignalingService::CreateSignaling(iomux, socketProviderLocal, socketProviderTCP, socketProviderTLS, webserverPluginProvider, appWebsocketClientProvider, appClientMedia->GetMediaProvider(),
                                                              signalingMediaConfig, appClientMedia->GetAudioIo(), appClientRinger->GetRingerIo(), appClientMedia->GetVideoIo(), NULL, NULL, NULL, NULL, NULL, &signalingServiceArgs);

        char signalingAppWorkingPath[256];
        _sprintf(signalingAppWorkingPath, "%s%c%s%c", get_files_dir(), get_separator_char(), "notifications", get_separator_char());
        if (!FileSystem::FileExists(signalingAppWorkingPath, 0) && !FileSystem::CreateDirectory(signalingAppWorkingPath, 0777, 0)) {
            debug->printf("CreateDirectory failed \"%s\"", signalingAppWorkingPath);
        }

#if (defined(_IOS_) && !TARGET_OS_IPHONE)
        signalingApp = new SignalingApp(iomux, browser, launcherApi, socketProviderLocal, socketProviderTCP, socketProviderTLS, dns, nullptr, browser, apirelayUrl, servicePassword, signalingAppWorkingPath, apirelayLocalPath, NULL, NULL, browser);
#else
        signalingApp = new SignalingApp(iomux, browser, launcherApi, socketProviderLocal, socketProviderTCP, socketProviderTLS, dns, nullptr, browser, apirelayUrl, servicePassword, signalingAppWorkingPath, apirelayLocalPath, NULL, browser, browser);
#endif
        logFlags->Add(signalingApp->GetAppName(), signalingApp->GetAppDomain(), signalingApp);

        signalingService->IAppStart(&signalingInstanceArgs);
        ISignaling * iSignaling = signalingService->GetISignalingInstance();

        logFlags->Add(signalingInstanceArgs.appName, signalingInstanceArgs.appDomain, iSignaling);
        iSignaling->SetSignalingUser(signalingApp);
        signalingApp->SetSignaling(iSignaling);
        launcherApi->AddLauncherApiUser(signalingApp);
        browser->SetSignalingApp(signalingApp);
        browser->AddCallKitUser(signalingApp);
        browser->AddNotificationsUser(signalingApp);
        browser->AddISignalingConfig(signalingApp);
    }

    launcherApi->SetServicePassword(servicePassword);
    _sprintf(tmp, "ws%s://%s:%u%s/audio", https, httpHostname, httpPort, medialibInstanceArgs.webserverPath);
    launcherApi->SetAudioServiceUrl(tmp);
    _sprintf(tmp, "ws%s://%s:%u%s/video", https, httpHostname, httpPort, medialibInstanceArgs.webserverPath);
    launcherApi->SetVideoServiceUrl(tmp);
    _sprintf(tmp, "ws%s://%s:%u%s/appsharing", https, httpHostname, httpPort, medialibInstanceArgs.webserverPath);
    launcherApi->SetAppSharingServiceUrl(tmp);
    _sprintf(tmp, "ws%s://%s:%u%s", https, httpHostname, httpPort, ringerInstanceArgs.webserverPath);
    launcherApi->SetRingerServiceUrl(tmp);
    if(appclientConfig->signalingApp) {
        _sprintf(tmp, "ws%s://%s:%u%s", https, httpHostname, httpPort, signalingInstanceArgs.webserverPath);
        launcherApi->SetSignalingServiceUrl(tmp);
    }

    browser->SetAutostartSettingsApp(autostartSettingsApp);
    logFlags->AddUser(this);

    i = _sprintf(url, "http%s://%s:%u%s/start.htm?launcherVersion=%s&launcherBuild=%s",
        https, httpHostname, httpPort, "/client", VERSION_STR,
#if ((defined(__ANDROID__) && !defined(NDEBUG)) || (defined(_IOS_) && defined(DEBUG)))
        "141A000"
#else
        BUILD_STRING
#endif
        );
    if (!noAppProxy) {
        i += _sprintf(&url[i], "&proxy=");
        _sprintf(tmp, "http%s://%s:%u%s/%s/", https, httpHostname, httpPort, appproxyInstanceArgs.webserverPath, APPPROXY_REGISTERED_PATH);
        i += str::to_url(tmp, &url[i], sizeof(url) - i);
    }
    i += _sprintf(&url[i], "&apirelay=");
    _sprintf(tmp, "ws%s://%s:%u%s/client", https, httpHostname, httpPort, apirelayInstanceArgs.webserverPath);
    i += str::to_url(tmp, &url[i], sizeof(url) - i);
    i += _sprintf(&url[i], "&servicePwd=");
    i += str::to_url(servicePassword, &url[i], sizeof(url) - i);
    if (webserverPortRetries > 0) {
        i += _sprintf(&url[i], "&portsChanged=true");
    }
    if (pbxServer && (strlen(pbxServer) < ((sizeof(url) - i) / 3))) {
        i += _sprintf(&url[i], "&server=");
        i += str::to_url(pbxServer, &url[i], sizeof(url) - i);
    }
    _sprintf(webserverUrl, "http://%s:%d/client", httpHostname, args->httpPort);
    _sprintf(fixCertUrl, "http://%s:%d/client/fixcert.htm", httpHostname, args->httpPort);
    str::to_url(servicePassword, tmp, sizeof(tmp));
    _sprintf(startFixCertUrl, "http://%s:%d/client/start.htm?action=fixcert&servicePwd=%s", httpHostname, args->httpPort, tmp);

    debug->printf("set url: %s", url);
    browser->SetUrl(url, webserverUrl, webserverPassword, startFixCertUrl, cert, certLen);
    browser->SetApiClientParameters(socketProviderLocal, socketProviderTCP, socketProviderTLS, dns, apirelayUrl, servicePassword, apirelayLocalPath);
}

AppClientMain::~AppClientMain()
{
    debug->printf("shutdown complete, deleting services");

    if(appWebsocketClientProvider) delete appWebsocketClientProvider;
    if(signalingService) delete signalingService;
    if(signalingApp) delete signalingApp;
    if(signalingMediaConfig) delete signalingMediaConfig;

    if (webserver) {
        delete appClientRinger;
        delete appClientMedia;
        delete urlHandler;
        delete launcherapiService;
        delete apirelayService;
        delete appproxyService;
#if (defined(__ANDROID__) || (defined(_IOS_) && !TARGET_OS_IPHONE))
        if (updateServiceService) delete updateServiceService;
#endif
        delete client;
        delete browser;
        delete webserver;
    }
    logFlags->RemoveUser(this);
    delete logFlags;
    delete webserverPluginProvider;
    delete socketProviderLocal;
    delete socketProviderTLS;
    delete socketProviderTCP;
    delete socketProviderUDP;
    delete dns;
    delete mediaConfiguration;
    free(cert);
    free(webserverPassword);
    free(encodedPassword);
    free(servicePassword);
    free(appArgv);
    debug->printf("deleting services complete");
}

void IAppClientMain::CloseListen()
{
    appClientMain->socketProviderUDP->CloseListen();
    appClientMain->socketProviderTCP->CloseListen();
}

void IAppClientMain::RestartListen()
{
    appClientMain->socketProviderUDP->RestartListen();
    appClientMain->socketProviderTCP->RestartListen();
}

void IAppClientMain::CloseTcpSockets()
{
    appClientMain->socketProviderTCP->CloseSockets();
}

int IAppClientMain::main(IIoMux * const iomux, UAppClientMain * const user, int argc, char *argv[])
{
    appClientMain = new AppClientMain(iomux, user, argc, argv);

    debug->printf("run");
    iomux->Run();

    delete appClientMain;
    // ----

    return 0;
}

void phone_registration_check()
{
    appClientMain->socketProviderTCP->CheckInterface();
    browser->RegistrationCheck();
    if (appWebsocketClientProvider) appWebsocketClientProvider->Ping();
    if (signalingApp) signalingApp->RegistrationCheck();
}

/*---------------------------------------------------------------------------*/
/*                                  ExternalApps                             */
/*---------------------------------------------------------------------------*/

ExternalApps::ExternalApps(class Browser * const browser, class IIoMux * const iomux, class IInstanceLog * const log, class ISocketProvider * localSocketProvider, const char * pwd, const char * dn, const char * localSocketPath) :
ApiWebsocketClient(iomux, localSocketProvider, nullptr, nullptr, nullptr, log, nullptr, pwd, dn, localSocketPath),
execCnt(0),
iomux(iomux),
log(log),
browser(browser)
{
    this->apiWebsocketClientClosed = false;
    this->closed = false;
    this->stopping = false;
    this->external = new ExternalAppsApiProvider(this, log);

    debug->printf("ExternalApps(%p)::ExternalApps()", this);
}

ExternalApps::~ExternalApps()
{
    debug->printf("ExternalApps(%p)::~ExternalApps() externalApiProvider=%p", this, this->external);
}

void ExternalApps::UpdateExternalApps(const char * externalApps)
{
    debug->printf("ExternalApps(%p)::UpdateExternalApps() externalApps=%s", this, externalApps ? externalApps : "");
    
    this->external->UpdateConsumers(externalApps);
}

void ExternalApps::IoExec(void * execContext)
{
    if(execContext == &__EXTERNAL_APPS_API_TRY_CLOSE) {
        if(stopping) TryClose();
    }
    execCnt--;
}

void ExternalApps::TryClose()
{
    debug->printf("ExternalApps(%p)::TryClose apiProvider=%p apiWebsocket=%d closed=%d execCnt=%d", this, this->external, apiWebsocketClientClosed, closed, execCnt);

    this->stopping = true;
    if(this->external) this->external->TryClose();
    else if(!apiWebsocketClientClosed) {  // ApiWebsocketClient not closed yet
        apiWebsocketClientClosed = true;

        ApiWebsocketClientClose();
    }
    else if(closed && !execCnt) {
        browser->ExternalAppsApiClientCloseComplete();
    }
}

void ExternalApps::ApiWebsocketClientCloseComplete()
{
    debug->printf("ExternalApps(%p)::ApiWebsocketClientCloseComplete stopping=%d closed=%d", this, apiWebsocketClientClosed, closed);

    this->external = NULL;
    closed = true;
    TryClose();
}

void ExternalApps::ExternalApiProviderClosed()
{
    debug->printf("ExternalApps(%p)::ExternalApiProviderClosed externalApiProvider=%p", this, this->external);

    this->external = NULL;
    TryClose();
}

/*---------------------------------------------------------------------------*/
/*                          ExternalAppsApiProvider                          */
/*---------------------------------------------------------------------------*/

ExternalAppsApiProvider::ExternalAppsApiProvider(class ExternalApps * parent, class IInstanceLog * const log) :
ApiProvider(parent, "com.innovaphone.externalapps"),
log(log),
stopping(false)
{
    debug->printf("ExternalAppsApiProvider(%p)::ExternalAppsApiProvider()", this);

    this->parent = parent;
    this->externalAppsStr = NULL;
}

ExternalAppsApiProvider::~ExternalAppsApiProvider()
{
    debug->printf("ExternalAppsApiProvider(%p)::~ExternalAppsApiProvider()", this);

    if(!consumers.empty()) {
        class ExternalAppsApiProviderHelper * c = consumers.front();
        while(c) {
            class ExternalAppsApiProviderHelper * n = c->goNext();
            delete c;
            c = n;
        }
    }
    if(this->externalAppsStr) free(this->externalAppsStr);
}

void ExternalAppsApiProvider::TryClose()
{
    debug->printf("ExternalAppsApiProvider(%p)::TryClose() startApps=%d", this, startExternalApps.empty());

    stopping = true;
    if(startExternalApps.empty()) {
        ApiProviderClosed();
        parent->ExternalApiProviderClosed();
    }
}

void ExternalAppsApiProvider::UpdateConsumers(const char * externalApps)
{
    if(this->externalAppsStr) free(this->externalAppsStr);
    this->externalAppsStr = _strdup(externalApps);
    
    class ExternalAppsApiProviderHelper * c = NULL;
    if(!consumers.empty()) {
        c = consumers.front();
        while(c) {
            UpdateExternalApps(c->client, c->consumer, c->src);
            c = c->goNext();
        }
    }
}

void ExternalAppsApiProvider::UpdateExternalApps(const char * client, const char * consumer, const char * src)
{
    char sb[128];
    class json_io send(sb);
    word base = send.add_object(JSON_ID_ROOT, 0);
    send.add_string(base, "mt", "ExternalAppsBegin");
    ApiProviderSend(client, consumer, src, send, base, sb);

    if(externalAppsStr) {
        debug->printf("ExternalAppsApiProvider(%p)::UpdateExternalApps external Apps (%s)", this, externalAppsStr);

        class json_io jsonApps(externalAppsStr);
        if(jsonApps.decode()) {
            word c_last = 0, t, end = 0xFFFF;
            word base = jsonApps.get_object(JSON_ID_ROOT, 0);
            word appsArray = jsonApps.get_array(base, "externalApps");
            while((t = jsonApps.get_object(appsArray, c_last)) != end) {
                if(jsonApps.get_string(t, "name") && jsonApps.get_string(t, "path")) {
                    bool present;
                    int appId = jsonApps.get_int(t, "id", &present);
                    const char * name = jsonApps.get_string(t, "name");
                    const char * path = jsonApps.get_string(t, "path");
                    const char * param = jsonApps.get_string(t, "param");
                    bool autostart = jsonApps.get_bool(t, "autostart");

                    if(present && name && path) {
                        char b[128];
                        char * tmp = b;
                        char sb[1024];
                        class json_io send(sb);
                        word base = send.add_object(JSON_ID_ROOT, 0);
                        send.add_string(base, "mt", "ExternalApp");
                        send.add_int(base, "id", appId, tmp);
                        send.add_string(base, "name", name);
                        send.add_string(base, "path", path);
                        send.add_string(base, "param", param);
                        send.add_bool(base, "autostart", autostart);
                        ApiProviderSend(client, consumer, src, send, base, sb);
                    }
                }
            }
        }
        else debug->printf("ExternalAppsApiProvider(%p)::UpdateExternalApps failed to decode external apps json", this);
    }
    
    send.reset();
    base = send.add_object(JSON_ID_ROOT, 0);
    send.add_string(base, "mt", "ExternalAppsEnd");
    ApiProviderSend(client, consumer, src, send, base, sb);
}

void ExternalAppsApiProvider::ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base)
{
    debug->printf("ExternalAppsApiProvider(%p)::ApiProviderRecv() client=%s consumer=%s src=%s", this, client, consumer, src);

    if(parent->apiWebsocketClientClosed) return;

    class ExternalAppsApiProviderHelper * c = NULL;
    if(!consumers.empty()) {
        c = consumers.front();
        while(c) {
            if( ((!client && !c->client) || (client && c->client && !strcmp(client, c->client))) &&
                ((!consumer && !c->consumer) || (consumer && c->consumer && !strcmp(consumer, c->consumer))) &&
                ((!src && !c->src) || (src && c->src && !strcmp(src, c->src)))) break;
            c = c->goNext();
        }
    }
    if(!c) {
        c = new class ExternalAppsApiProviderHelper(client, consumer, src);
        consumers.push_back(c);
    }

    const char * mt = msg.get_string(base, "mt");
    if(mt) {
        debug->printf("ExternalAppsApiProvider(%p)::ApiProviderRecv() mt=%s", this, mt);

        if(!strcmp(mt, "StartExternalApp")) {
            bool present;
            msg.get_int(base, "id", &present);
            if(present && msg.get_string(base, "path")) {
                const char * path = msg.get_string(base, "path");
                const char * param = msg.get_string(base, "param");
                debug->printf("ExternalAppsApiProvider::StartExternalApp (%s) arguments (%s)", path, param);

                appClientMainUser->AppClientMainStartExternalApp(_strdup(path), _strdup(param));
            }
        }
        else if(!strcmp(mt, "GetExternalApps")) {
            UpdateExternalApps(c->client, c->consumer, c->src);
        }
    }
}

void ExternalAppsApiProvider::ApiProviderConsumerClosed(const char * client, const char * consumer)
{
    debug->printf("ExternalAppsApiProvider(%p)::ApiProviderConsumerClosed() client=%s consumer=%s", this, client ? client : "", consumer ? consumer : "");
}

/*----------------------------------------------------------------------------*/
