"use strict";
/*
 * server component for the TimeLimit App
 * Copyright (C) 2019 - 2022 Jonas Lochmann
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyActionsFromDevice = void 0;
const http_errors_1 = require("http-errors");
const database_1 = require("../../../database");
const websocket_1 = require("../../websocket");
const baseinfo_1 = require("./baseinfo");
const cache_1 = require("./cache");
const dispatch_helper_1 = require("./dispatch-helper");
const exception_1 = require("./exception");
const illegal_state_1 = require("./exception/illegal-state");
const sequence_1 = require("./exception/sequence");
const integrity_1 = require("./integrity");
const applyActionsFromDevice = async ({ database, request, websocket, connectedDevicesManager, eventHandler }) => {
    eventHandler.countEvent('applyActionsFromDevice');
    if (request.actions.length > 50) {
        eventHandler.countEvent('applyActionsFromDevice tooMuchActionsPerRequest');
        throw new http_errors_1.BadRequest();
    }
    return database.transaction(async (transaction) => {
        const baseInfo = await (0, baseinfo_1.getApplyActionBaseInfo)({ database, transaction, deviceAuthToken: request.deviceAuthToken });
        const cache = new cache_1.Cache({
            database,
            hasFullVersion: baseInfo.hasFullVersion,
            transaction,
            familyId: baseInfo.familyId,
            deviceId: baseInfo.deviceId,
            connectedDevicesManager
        });
        let { nextSequenceNumber } = baseInfo;
        for (const action of request.actions) {
            try {
                if (action.sequenceNumber < nextSequenceNumber) {
                    // action was already received
                    throw new sequence_1.SequenceNumberRepeatedException();
                }
                await cache.subtransaction(async () => {
                    // update the next sequence number
                    nextSequenceNumber = action.sequenceNumber + 1;
                    if (action.type === 'appLogic') {
                        await (0, dispatch_helper_1.dispatchAppLogicAction)({
                            action,
                            cache,
                            deviceId: baseInfo.deviceId,
                            eventHandler
                        });
                    }
                    else if (action.type === 'parent') {
                        const { isChildLimitAdding, authentication } = await (0, integrity_1.assertActionIntegrity)({
                            deviceId: baseInfo.deviceId,
                            cache,
                            action
                        });
                        await (0, dispatch_helper_1.dispatchParentAction)({
                            action,
                            cache,
                            deviceId: baseInfo.deviceId,
                            eventHandler,
                            isChildLimitAdding,
                            authentication
                        });
                    }
                    else if (action.type === 'child') {
                        await (0, integrity_1.assertActionIntegrity)({
                            deviceId: baseInfo.deviceId,
                            cache,
                            action
                        });
                        await (0, dispatch_helper_1.dispatchChildAction)({
                            action,
                            cache,
                            childUserId: action.userId,
                            deviceId: baseInfo.deviceId,
                            eventHandler
                        });
                    }
                    else {
                        throw new illegal_state_1.IllegalStateException({ staticMessage: 'not possible action.type value' });
                    }
                });
            }
            catch (ex) {
                if ((0, database_1.shouldRetryWithException)(database, ex)) {
                    eventHandler.countEvent('applyActionsFromDevice got exception which should cause retry');
                    throw ex;
                }
                else if (ex instanceof exception_1.ApplyActionException) {
                    eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction:' + ex.staticMessage);
                }
                else {
                    const stack = ex instanceof Error && ex.stack ? ex.stack.substring(0, 4096) : 'no stack';
                    eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction:other:' + stack);
                }
                cache.requireSenderFullSync();
            }
        }
        // save new next sequence number
        if (nextSequenceNumber !== baseInfo.nextSequenceNumber) {
            eventHandler.countEvent('applyActionsFromDevice updateSequenceNumber');
            await database.device.update({
                nextSequenceNumber
            }, {
                where: {
                    familyId: baseInfo.familyId,
                    deviceId: baseInfo.deviceId
                },
                transaction
            });
        }
        await cache.saveModifiedVersionNumbers();
        await (0, websocket_1.notifyClientsAboutChangesDelayed)({
            familyId: baseInfo.familyId,
            sourceDeviceId: baseInfo.deviceId,
            websocket,
            database,
            transaction,
            generalLevel: cache.triggeredSyncLevel,
            targetedLevels: cache.targetedTriggeredSyncLevels
        });
        if (cache.triggeredSyncLevel === 2) {
            transaction.afterCommit(() => {
                eventHandler.countEvent('applyActionsFromDevice areChangesImportant');
            });
        }
        else if ([...cache.targetedTriggeredSyncLevels.entries()].some((entry) => entry[1] === 2)) {
            transaction.afterCommit(() => {
                eventHandler.countEvent('applyActionsFromDevice areChangesImportantTargeted');
            });
        }
        return {
            shouldDoFullSync: cache.isSenderDoFullSyncTrue()
        };
    });
};
exports.applyActionsFromDevice = applyActionsFromDevice;
//# sourceMappingURL=index.js.map