@zeniai/client-epic-state 5.0.11-betaRR2 → 5.0.12-beta0ND
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/common/aiCfo/aiCfoSuggestedQuestionsPageContext.d.ts +1 -1
- package/lib/coreEpics.js +2 -1
- package/lib/entity/file/fileState.d.ts +4 -4
- package/lib/entity/file/fileState.js +0 -4
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +0 -2
- package/lib/entity/task/taskPayload.d.ts +0 -3
- package/lib/entity/task/taskPayload.js +0 -5
- package/lib/entity/task/taskState.d.ts +0 -3
- package/lib/entity/tenant/SessionManager.d.ts +54 -0
- package/lib/entity/tenant/SessionManager.js +290 -0
- package/lib/entity/tenant/epic/sessionHeartbeatEpic.d.ts +16 -0
- package/lib/entity/tenant/epic/sessionHeartbeatEpic.js +16 -0
- package/lib/entity/tenant/sessionTypes.d.ts +26 -0
- package/lib/entity/tenant/sessionTypes.js +12 -0
- package/lib/entity/tenant/tenantReducer.d.ts +5 -1
- package/lib/entity/tenant/tenantReducer.js +23 -2
- package/lib/entity/tenant/tenantState.d.ts +1 -0
- package/lib/entity/transaction/stateTypes/attachment.d.ts +2 -2
- package/lib/epic.d.ts +1 -3
- package/lib/epic.js +1 -3
- package/lib/esm/coreEpics.js +2 -1
- package/lib/esm/entity/file/fileState.js +0 -4
- package/lib/esm/entity/snackbar/snackbarTypes.js +0 -2
- package/lib/esm/entity/task/taskPayload.js +0 -5
- package/lib/esm/entity/tenant/SessionManager.js +286 -0
- package/lib/esm/entity/tenant/epic/sessionHeartbeatEpic.js +12 -0
- package/lib/esm/entity/tenant/sessionTypes.js +9 -0
- package/lib/esm/entity/tenant/tenantReducer.js +21 -1
- package/lib/esm/epic.js +1 -3
- package/lib/esm/index.js +9 -6
- package/lib/esm/view/taskManager/taskDetailView/epics/archiveTaskEpic.js +0 -3
- package/lib/esm/view/taskManager/taskDetailView/epics/deleteTaskEpic.js +0 -5
- package/lib/esm/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +0 -1
- package/lib/esm/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +0 -1
- package/lib/esm/view/taskManager/taskDetailView/taskDetail.js +0 -4
- package/lib/esm/view/taskManager/taskDetailView/taskDetailReducer.js +1 -51
- package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +2 -15
- package/lib/esm/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.js +21 -56
- package/lib/esm/view/taskManager/taskListView/epics/dragNDropTasksEpic.js +0 -1
- package/lib/esm/view/taskManager/taskListView/epics/fetchTaskListEpic.js +3 -13
- package/lib/esm/view/taskManager/taskListView/epics/updateTaskFromListViewEpic.js +2 -3
- package/lib/esm/view/taskManager/taskListView/taskList.js +0 -7
- package/lib/esm/view/taskManager/taskListView/taskListReducer.js +12 -91
- package/lib/esm/view/taskManager/taskListView/taskListSelector.js +39 -48
- package/lib/index.d.ts +9 -6
- package/lib/index.js +36 -35
- package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/taskManager/taskDetailView/epics/archiveTaskEpic.d.ts +1 -2
- package/lib/view/taskManager/taskDetailView/epics/archiveTaskEpic.js +0 -3
- package/lib/view/taskManager/taskDetailView/epics/deleteTaskEpic.d.ts +1 -2
- package/lib/view/taskManager/taskDetailView/epics/deleteTaskEpic.js +0 -5
- package/lib/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +0 -1
- package/lib/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +0 -1
- package/lib/view/taskManager/taskDetailView/taskDetail.d.ts +0 -2
- package/lib/view/taskManager/taskDetailView/taskDetail.js +0 -4
- package/lib/view/taskManager/taskDetailView/taskDetailReducer.d.ts +1 -10
- package/lib/view/taskManager/taskDetailView/taskDetailReducer.js +2 -52
- package/lib/view/taskManager/taskDetailView/taskDetailSelector.d.ts +0 -5
- package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +2 -15
- package/lib/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.d.ts +7 -6
- package/lib/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.js +22 -56
- package/lib/view/taskManager/taskListView/epics/dragNDropTasksEpic.js +0 -1
- package/lib/view/taskManager/taskListView/epics/fetchTaskListEpic.js +3 -13
- package/lib/view/taskManager/taskListView/epics/updateTaskFromListViewEpic.js +2 -3
- package/lib/view/taskManager/taskListView/taskList.d.ts +1 -14
- package/lib/view/taskManager/taskListView/taskList.js +1 -8
- package/lib/view/taskManager/taskListView/taskListPayload.d.ts +0 -3
- package/lib/view/taskManager/taskListView/taskListReducer.d.ts +2 -6
- package/lib/view/taskManager/taskListView/taskListReducer.js +13 -92
- package/lib/view/taskManager/taskListView/taskListSelector.d.ts +3 -9
- package/lib/view/taskManager/taskListView/taskListSelector.js +39 -48
- package/package.json +1 -1
- package/lib/esm/view/taskManager/taskDetailView/epics/snoozeTaskEpic.js +0 -65
- package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +0 -56
- package/lib/view/taskManager/taskDetailView/epics/snoozeTaskEpic.d.ts +0 -9
- package/lib/view/taskManager/taskDetailView/epics/snoozeTaskEpic.js +0 -69
- package/lib/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.d.ts +0 -9
- package/lib/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +0 -60
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @see README.md in this directory for maintenance expectations.
|
|
18
18
|
*/
|
|
19
|
-
export type AiCfoSuggestedQuestionsPageContext = 'balance_sheet' | 'profit_loss' | 'vendors' | 'transactions' | 'cash_flow' | 'accounts_receivable' | 'accounts_payable' | 'transaction_details' | 'dashboard' | 'cash_balance' | 'card_balance' | 'command_centre' | 'je_schedules' | 'reconciliation' | 'flux_analysis' | 'receipts' | 'mei' | 'task_checklist' | 'performance' | 'bills_list' | 'bills_details' | 'reimbursement_list' | 'reimbursement_details' | 'vendors_summary' | 'opex_by_vendor' | 'prepaid_expenses' | 'fixed_assets' | 'form_1099' | 'accrued_expenses' | 'forecast' | 'checking' | 'checking_details' | 'treasury' | 'cards' | 'cards_detail' | 'people' | 'rewards' | 'notifications';
|
|
19
|
+
export type AiCfoSuggestedQuestionsPageContext = 'balance_sheet' | 'profit_loss' | 'vendors' | 'transactions' | 'cash_flow' | 'accounts_receivable' | 'accounts_payable' | 'transaction_details' | 'dashboard' | 'cash_balance' | 'card_balance' | 'cash_in_cash_out' | 'net_burn_or_income' | 'cash_position' | 'top_expenses' | 'revenue' | 'operating_expenses' | 'command_centre' | 'je_schedules' | 'reconciliation' | 'flux_analysis' | 'receipts' | 'mei' | 'task_checklist' | 'performance' | 'bills_list' | 'bills_details' | 'reimbursement_list' | 'reimbursement_details' | 'vendors_summary' | 'opex_by_vendor' | 'prepaid_expenses' | 'fixed_assets' | 'form_1099' | 'accrued_expenses' | 'forecast' | 'checking' | 'checking_details' | 'treasury' | 'cards' | 'cards_detail' | 'people' | 'rewards' | 'notifications';
|
package/lib/coreEpics.js
CHANGED
|
@@ -24,6 +24,7 @@ const resendVerifyDeviceOTPEpic_1 = require("./entity/tenant/epic/resendVerifyDe
|
|
|
24
24
|
const saveExternalConnectionEpic_1 = require("./entity/tenant/epic/saveExternalConnectionEpic");
|
|
25
25
|
const sendEmailMagicLinkToUserEpic_1 = require("./entity/tenant/epic/sendEmailMagicLinkToUserEpic");
|
|
26
26
|
const signInUserEpic_1 = require("./entity/tenant/epic/signInUserEpic");
|
|
27
|
+
const sessionHeartbeatEpic_1 = require("./entity/tenant/epic/sessionHeartbeatEpic");
|
|
27
28
|
const signOutUserEpic_1 = require("./entity/tenant/epic/signOutUserEpic");
|
|
28
29
|
const verifyDeviceWithTwoFAEpic_1 = require("./entity/tenant/epic/verifyDeviceWithTwoFAEpic");
|
|
29
30
|
// ── Toast Notification Epic ──────────────────────────────────────────
|
|
@@ -48,7 +49,7 @@ const verifyOtpEpic_1 = require("./view/twoFactorAuthentication/verifyOtpEpic");
|
|
|
48
49
|
// RootActionType is only available in the dynamically imported epic.ts.
|
|
49
50
|
const coreRootEpic = (0, redux_observable_1.combineEpics)(
|
|
50
51
|
// Auth & tenant
|
|
51
|
-
clearAllEpic_1.clearAllEpic, doMagicLinkSignInEpic_1.doMagicLinkSignInEpic, signInUserEpic_1.doSignInEpic, signOutUserEpic_1.doSignOutEpic, fetchActiveTenantEpic_1.fetchActiveTenantEpic, fetchAllTenantsEpic_1.fetchAllTenantsEpic, fetchExcludedResourcesEpic_1.fetchExcludedResourcesEpic, fetchExternalConnectionsEpic_1.fetchExternalConnectionsEpic, fetchSubscriptionSummaryForTenantEpic_1.fetchSubscriptionSummaryForTenantEpic, resendVerifyDeviceOTPEpic_1.resendVerifyDeviceOTPEpic, saveExternalConnectionEpic_1.saveExternalConnectionEpic, sendEmailMagicLinkToUserEpic_1.sendEmailMagicLinkToUserEpic, verifyDeviceWithTwoFAEpic_1.verifyDeviceWithTwoFAEpic,
|
|
52
|
+
clearAllEpic_1.clearAllEpic, doMagicLinkSignInEpic_1.doMagicLinkSignInEpic, signInUserEpic_1.doSignInEpic, signOutUserEpic_1.doSignOutEpic, sessionHeartbeatEpic_1.sessionHeartbeatEpic, fetchActiveTenantEpic_1.fetchActiveTenantEpic, fetchAllTenantsEpic_1.fetchAllTenantsEpic, fetchExcludedResourcesEpic_1.fetchExcludedResourcesEpic, fetchExternalConnectionsEpic_1.fetchExternalConnectionsEpic, fetchSubscriptionSummaryForTenantEpic_1.fetchSubscriptionSummaryForTenantEpic, resendVerifyDeviceOTPEpic_1.resendVerifyDeviceOTPEpic, saveExternalConnectionEpic_1.saveExternalConnectionEpic, sendEmailMagicLinkToUserEpic_1.sendEmailMagicLinkToUserEpic, verifyDeviceWithTwoFAEpic_1.verifyDeviceWithTwoFAEpic,
|
|
52
53
|
// Toast
|
|
53
54
|
pushToastNotificationEpic_1.pushToastNotificationEpic,
|
|
54
55
|
// Collaboration
|
|
@@ -43,16 +43,16 @@ export interface AttachmentFile {
|
|
|
43
43
|
fileName: string;
|
|
44
44
|
}
|
|
45
45
|
export type FileSizeUnit = 'bytes';
|
|
46
|
-
declare const toSupportedFileType: (v: string) => "application/pdf" | "
|
|
47
|
-
export declare const toSupportedFileTypeStrict: (v: string) => "application/pdf" | "
|
|
46
|
+
declare const toSupportedFileType: (v: string) => "application/pdf" | "image/jpeg" | "image/jpg" | "image/png" | "image/bmp" | "image/heic" | "text/html" | "text/csv" | "text/plain";
|
|
47
|
+
export declare const toSupportedFileTypeStrict: (v: string) => "application/pdf" | "image/jpeg" | "image/jpg" | "image/png" | "image/bmp" | "image/heic" | "text/html" | "text/csv" | "text/plain" | undefined;
|
|
48
48
|
export type SupportedFileType = ReturnType<typeof toSupportedFileType>;
|
|
49
49
|
declare const toSupportedFileFormatType: (v: string) => ".pdf" | ".jpeg" | ".jpg" | ".bmp" | ".heic" | ".png" | ".csv" | ".txt";
|
|
50
50
|
export type SupportedFileFormatType = ReturnType<typeof toSupportedFileFormatType>;
|
|
51
51
|
export declare const SUPPORTED_ATTACHMENT_FILE_TYPES: readonly ["application/pdf", "image/jpeg", "image/jpg", "image/png", "text/plain", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"];
|
|
52
|
-
export declare const toSupportedAttachmentFileType: (v: string) => "application/pdf" | "
|
|
52
|
+
export declare const toSupportedAttachmentFileType: (v: string) => "application/pdf" | "image/jpeg" | "image/jpg" | "image/png" | "text/plain" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
53
53
|
export type SupportedAttachmentFileType = ReturnType<typeof toSupportedAttachmentFileType>;
|
|
54
54
|
export declare const SUPPORTED_TRANSACTION_LINE_ATTACHMENT_FILE_TYPES: readonly ["application/x-excel", "application/pdf", "image/jpeg", "image/jpg", "image/png", "text/csv", "image/tiff", "image/tif", "image/gif", "text/xml", "application/xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/msword"];
|
|
55
|
-
export declare const toSupportedTransactionLineAttachmentFileType: (v: string) => "application/pdf" | "
|
|
55
|
+
export declare const toSupportedTransactionLineAttachmentFileType: (v: string) => "application/pdf" | "image/jpeg" | "image/jpg" | "image/png" | "text/csv" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/x-excel" | "image/tiff" | "image/tif" | "image/gif" | "text/xml" | "application/xml" | "application/msword";
|
|
56
56
|
export type SupportedTransactionLineAttachmentFileType = ReturnType<typeof toSupportedTransactionLineAttachmentFileType>;
|
|
57
57
|
export declare const toSupportedAttachmentFileFormatType: (v: string) => ".pdf" | ".jpeg" | ".jpg" | ".png" | ".txt" | ".xls" | ".xlsx";
|
|
58
58
|
type SupportedAttachmentFileFormatType = ReturnType<typeof toSupportedAttachmentFileFormatType>;
|
|
@@ -4,10 +4,6 @@ exports.toAllowedDocumentTypeStrict = exports.toAllowedDocumentType = exports.to
|
|
|
4
4
|
const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
|
|
5
5
|
const SUPPORTED_FILE_TYPES = [
|
|
6
6
|
'application/pdf',
|
|
7
|
-
'application/msword',
|
|
8
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
9
|
-
'application/vnd.ms-excel',
|
|
10
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
11
7
|
'image/jpeg',
|
|
12
8
|
'image/jpg',
|
|
13
9
|
'image/png',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const toSnackbarMessageSection: (v: string) => "common" | "transactionDetails_updatingPastTransactions" | "transactionDetails_transactionUpdated" | "cockpit_month_end_email_sent" | "cockpit_month_end_email_save" | "cockpit_month_end_email_attachment_upload" | "je_bill_link" | "je_posted" | "audit_score_updated" | "people_invite_people" | "people_invite_multiple_people" | "people_delete_person" | "people_update_person" | "reimbursement_updated" | "reimbursement_deleted" | "reimbursement_cancelled_deleted" | "reimbursement_cancelled" | "reimbursement_approved" | "reimbursement_rejected" | "reimbursement_sent_for_approval" | "account_added" | "business_verification_save" | "business_verification_submit" | "business_verification_from_bills_submit" | "plaid_connection" | "unlink_deposit_account" | "reimbursement_setup" | "reimbursement_approval_create" | "reimbursement_duplicate_approval_create" | "reimbursement_approval_update" | "reimbursement_duplicate_approval_update" | "reimbursement_approval_delete" | "reimbursement_create_mileage" | "reimbursement_update_mileage" | "reimbursement_accept_term" | "reimbursement_accept_employee_term" | "reimbursement_bulk_submit" | "reimbursement_bulk_processed" | "bill_pay_setup" | "zeni_accounts_setup" | "bill_pay_approval_create" | "bill_pay_duplicate_approval_create" | "bill_pay_approval_update" | "bill_pay_duplicate_approval_update" | "bill_pay_approval_delete" | "bill_pay_updated" | "bill_pay_deleted" | "bill_pay_cancelled_deleted" | "bill_pay_cancelled" | "bill_pay_approved" | "real_time_approver_added" | "bill_pay_rejected" | "bill_pay_sent_for_approval" | "bill_pay_accept_term" | "bill_pay_bulk_submit" | "bill_pay_bulk_processed" | "bill_pay_refund" | "bill_pay_retry" | "bill_pay_marked_as_paid" | "zeni_account_accept_term" | "update_vendor" | "data_refresh_update" | "delete_bank_account" | "create_bank_account" | "create_bank_account_ach" | "create_bank_account_wire" | "create_bank_account_international" | "transfer_money" | "update_zeni_account_nickname" | "create_checking_account" | "deposit_check" | "create_vendor" | "onboarding_customer_view" | "onboarding_customer_view_complete" | "onboarding_customer_identity_verification_save" | "onboarding_customer_identity_verification_submit" | "onboarding_customer_business_verification_save" | "onboarding_customer_business_verification_submit" | "retry_bank_account_connection" | "dashboard_invite_sent" | "onboarding_info_saved" | "approve_original_merchant" | "approve_global_merchant" | "reject_global_merchant" | "create_global_merchant" | "fetch_global_merchant_no_recommendation" | "save_vendor_renamed" | "save_vendor_sent_for_review" | "save_vendor_marked_as_employee" | "save_vendor_marked_as_local_contractor" | "charge_card_setup" | "charge_card_accept_term" | "charge_card_receipt_upload" | "charge_card_resend_invite" | "charge_card_revoke_invite" | "charge_card_update_limit" | "charge_cards_update_limit" | "close_charge_card" | "lock_charge_card" | "lock_charge_card_card_user" | "lock_charge_cards" | "unlock_charge_card" | "unlock_charge_card_card_user" | "unlock_charge_cards" | "close_charge_cards" | "revoke_invite_charge_cards" | "charge_card_express_interest" | "create_schedule" | "save_schedule" | "delete_schedule" | "ignore_schedule" | "save_task_detail" | "fetch_task_detail" | "delete_task" | "archive_task" | "
|
|
1
|
+
export declare const toSnackbarMessageSection: (v: string) => "common" | "transactionDetails_updatingPastTransactions" | "transactionDetails_transactionUpdated" | "cockpit_month_end_email_sent" | "cockpit_month_end_email_save" | "cockpit_month_end_email_attachment_upload" | "je_bill_link" | "je_posted" | "audit_score_updated" | "people_invite_people" | "people_invite_multiple_people" | "people_delete_person" | "people_update_person" | "reimbursement_updated" | "reimbursement_deleted" | "reimbursement_cancelled_deleted" | "reimbursement_cancelled" | "reimbursement_approved" | "reimbursement_rejected" | "reimbursement_sent_for_approval" | "account_added" | "business_verification_save" | "business_verification_submit" | "business_verification_from_bills_submit" | "plaid_connection" | "unlink_deposit_account" | "reimbursement_setup" | "reimbursement_approval_create" | "reimbursement_duplicate_approval_create" | "reimbursement_approval_update" | "reimbursement_duplicate_approval_update" | "reimbursement_approval_delete" | "reimbursement_create_mileage" | "reimbursement_update_mileage" | "reimbursement_accept_term" | "reimbursement_accept_employee_term" | "reimbursement_bulk_submit" | "reimbursement_bulk_processed" | "bill_pay_setup" | "zeni_accounts_setup" | "bill_pay_approval_create" | "bill_pay_duplicate_approval_create" | "bill_pay_approval_update" | "bill_pay_duplicate_approval_update" | "bill_pay_approval_delete" | "bill_pay_updated" | "bill_pay_deleted" | "bill_pay_cancelled_deleted" | "bill_pay_cancelled" | "bill_pay_approved" | "real_time_approver_added" | "bill_pay_rejected" | "bill_pay_sent_for_approval" | "bill_pay_accept_term" | "bill_pay_bulk_submit" | "bill_pay_bulk_processed" | "bill_pay_refund" | "bill_pay_retry" | "bill_pay_marked_as_paid" | "zeni_account_accept_term" | "update_vendor" | "data_refresh_update" | "delete_bank_account" | "create_bank_account" | "create_bank_account_ach" | "create_bank_account_wire" | "create_bank_account_international" | "transfer_money" | "update_zeni_account_nickname" | "create_checking_account" | "deposit_check" | "create_vendor" | "onboarding_customer_view" | "onboarding_customer_view_complete" | "onboarding_customer_identity_verification_save" | "onboarding_customer_identity_verification_submit" | "onboarding_customer_business_verification_save" | "onboarding_customer_business_verification_submit" | "retry_bank_account_connection" | "dashboard_invite_sent" | "onboarding_info_saved" | "approve_original_merchant" | "approve_global_merchant" | "reject_global_merchant" | "create_global_merchant" | "fetch_global_merchant_no_recommendation" | "save_vendor_renamed" | "save_vendor_sent_for_review" | "save_vendor_marked_as_employee" | "save_vendor_marked_as_local_contractor" | "charge_card_setup" | "charge_card_accept_term" | "charge_card_receipt_upload" | "charge_card_resend_invite" | "charge_card_revoke_invite" | "charge_card_update_limit" | "charge_cards_update_limit" | "close_charge_card" | "lock_charge_card" | "lock_charge_card_card_user" | "lock_charge_cards" | "unlock_charge_card" | "unlock_charge_card_card_user" | "unlock_charge_cards" | "close_charge_cards" | "revoke_invite_charge_cards" | "charge_card_express_interest" | "create_schedule" | "save_schedule" | "delete_schedule" | "ignore_schedule" | "save_task_detail" | "fetch_task_detail" | "delete_task" | "archive_task" | "create_tag" | "delete_tag" | "update_charge_card" | "update_charge_card_name" | "update_charge_card_name_card_user" | "issue_charge_card" | "issue_charge_cards" | "notification_settings_saved" | "referral_invite_sent" | "notification_mark_as_read" | "mark_as_complete_schedule" | "cancel_journal_entry" | "settings_accounting_accounts_updated" | "create_card_setup" | "confirm_card_setup" | "add_card_payment_source" | "fetch_payment_sources" | "task_assigned_toast_notification" | "task_due_tomorrow_toast_notification" | "task_deleted_toast_notification" | "task_archived_toast_notification" | "task_overdue_toast_notification" | "task_overdue_toast_notification_creator" | "task_notification_count" | "task_activities_toast_notification" | "task_created_toast_notification" | "task_group_creation_success" | "task_group_deletion_success" | "task_group_update_success" | "primary_funding_account_updated" | "task_time_spent_validation" | "missing_receipts_attachment" | "flux_analysis_unreviewed" | "flux_analysis_reviewed" | "receipt_match" | "receipts_upload" | "receipts_bulk_match" | "billing_address_view" | "express_pay_submit" | "exclude_transaction" | "reconcile" | "save_reconcile_for_later" | "recon_transaction_categorize" | "recon_transaction_match" | "update_debit_card_pin_attempt" | "set_debit_card_pin" | "ai_cfo_create_session_and_submit" | "ai_cfo_chat_session_deleted" | "charge_card_auto_pay_enable" | "charge_card_auto_pay_disable" | "treasury_setup" | "treasury_accept_term" | "treasury_transfer_money" | "treasury_transfer_money_failed" | "auto_transfer_rule_create" | "auto_transfer_rule_update" | "auto_transfer_rule_delete" | "auto_transfer_rule_pause" | "auto_transfer_rule_resume" | "treasury_update_portfolio_allocation" | "send_email_magic_link_to_user" | "complete_profile_done" | "reports_resync" | "invalid_phone_number" | "transactions_categorized_updated_failed" | "transaction_categorized_updated_failed" | "transactionsCategorized_transactionsUpdated" | "transactionsCategorized_transactionUpdated" | "transactionCategorized_transactionsUpdated" | "transactionCategorized_transactionUpdated" | "transactionsCategorized_transactionsFailed" | "transactionsCategorized_transactionFailed" | "transactionCategorized_transactionsFailed" | "transactionCategorized_transactionFailed" | "transactionsUpdated_transactionsFailed" | "transactionsUpdated_transactionFailed" | "transactionUpdated_transactionsFailed" | "transactionUpdated_transactionFailed" | "transactionsCategorized_transactionsUpdated_transactionsFailed" | "transactionsCategorized_transactionsUpdated_transactionFailed" | "transactionsCategorized_transactionUpdated_transactionsFailed" | "transactionCategorized_transactionsUpdated_transactionsFailed" | "transactionsCategorized_transactionUpdated_transactionFailed" | "transactionCategorized_transactionsUpdated_transactionFailed" | "transactionCategorized_transactionUpdated_transactionsFailed" | "transactionCategorized_transactionUpdated_transactionFailed" | "accounting_classes_enabled_update" | "account_excluded_from_reconciliation" | "account_included_in_reconciliation" | "ai_accountant_trigger_job";
|
|
2
2
|
export type SnackbarMessageSections = ReturnType<typeof toSnackbarMessageSection>;
|
|
3
3
|
export declare const toSnackbarMessageSectionTexts: (v: string) => "notification" | "failed" | "success";
|
|
4
4
|
export type SnackbarMessageSectionTexts = ReturnType<typeof toSnackbarMessageSectionTexts>;
|
|
@@ -9,7 +9,6 @@ export interface TaskPayload {
|
|
|
9
9
|
file_ids: string[];
|
|
10
10
|
is_archived: boolean;
|
|
11
11
|
is_deleted: boolean;
|
|
12
|
-
is_private: boolean;
|
|
13
12
|
name: string;
|
|
14
13
|
priority: AllowedValueWithCodePayload;
|
|
15
14
|
recurring_end_date: string | null;
|
|
@@ -23,8 +22,6 @@ export interface TaskPayload {
|
|
|
23
22
|
update_time: string | null;
|
|
24
23
|
company_id?: string;
|
|
25
24
|
group_assignees?: string[];
|
|
26
|
-
recurring_source_task_id?: string | null;
|
|
27
|
-
snoozed_until?: string | null;
|
|
28
25
|
sync_token?: string | null;
|
|
29
26
|
task_group_ids?: string[] | null;
|
|
30
27
|
}
|
|
@@ -13,7 +13,6 @@ const mapTaskPayloadToTask = (payload) => ({
|
|
|
13
13
|
fileIds: payload.file_ids,
|
|
14
14
|
isArchived: payload.is_archived,
|
|
15
15
|
isDeleted: payload.is_deleted,
|
|
16
|
-
isPrivate: payload.is_private,
|
|
17
16
|
name: payload.name,
|
|
18
17
|
syncToken: payload.sync_token ?? '',
|
|
19
18
|
priority: {
|
|
@@ -39,10 +38,6 @@ const mapTaskPayloadToTask = (payload) => ({
|
|
|
39
38
|
dueDate: payload.due_date != null ? (0, zeniDayJS_1.date)(payload.due_date) : undefined,
|
|
40
39
|
updateTime: payload.update_time != null ? (0, zeniDayJS_1.date)(payload.update_time) : undefined,
|
|
41
40
|
companyId: payload.company_id,
|
|
42
|
-
recurringSourceTaskId: payload.recurring_source_task_id != null
|
|
43
|
-
? payload.recurring_source_task_id
|
|
44
|
-
: undefined,
|
|
45
|
-
snoozedUntil: payload.snoozed_until != null ? (0, zeniDayJS_1.date)(payload.snoozed_until) : undefined,
|
|
46
41
|
taskGroupIds: payload.task_group_ids != null ? payload.task_group_ids : [],
|
|
47
42
|
timeSpent: payload.time_spent != null
|
|
48
43
|
? (0, formatMinutesToFromHHMM_1.convertMinutesToHHMM)(payload.time_spent)
|
|
@@ -21,12 +21,9 @@ export interface Task {
|
|
|
21
21
|
type: TaskType;
|
|
22
22
|
companyId?: ID;
|
|
23
23
|
dueDate?: ZeniDate;
|
|
24
|
-
isPrivate?: boolean;
|
|
25
24
|
recurringEndDate?: ZeniDate;
|
|
26
25
|
recurringFrequency?: RecurringFrequencyType;
|
|
27
|
-
recurringSourceTaskId?: ID;
|
|
28
26
|
recurringStartDate?: ZeniDate;
|
|
29
|
-
snoozedUntil?: ZeniDate | null;
|
|
30
27
|
updateTime?: ZeniDate;
|
|
31
28
|
}
|
|
32
29
|
export interface TaskState {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionManager — Central session lifecycle manager.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* 1. Track user activity (mousemove, keydown, click, scroll, touch)
|
|
6
|
+
* 2. Send heartbeat at configurable intervals — only when user has been active
|
|
7
|
+
* 3. Show warning popup after idle timeout
|
|
8
|
+
* 4. Auto-logout after warning countdown expires
|
|
9
|
+
* 5. Sync activity across same-origin tabs via BroadcastChannel so one
|
|
10
|
+
* active tab keeps other tabs' sessions alive (no cascading logout).
|
|
11
|
+
*/
|
|
12
|
+
import { SessionCallbacks, SessionConfig } from './sessionTypes';
|
|
13
|
+
export declare class SessionManager {
|
|
14
|
+
private config;
|
|
15
|
+
private callbacks;
|
|
16
|
+
/** Timestamps and timers */
|
|
17
|
+
private lastActivityTime;
|
|
18
|
+
/** Timestamp of the last heartbeat fire (initial/periodic/continue/activity). */
|
|
19
|
+
private lastHeartbeatTime;
|
|
20
|
+
/** Set to true whenever user (or any same-origin tab) shows activity. */
|
|
21
|
+
private hadActivitySinceLastHeartbeat;
|
|
22
|
+
private idleCheckInterval;
|
|
23
|
+
private heartbeatInterval;
|
|
24
|
+
private countdownInterval;
|
|
25
|
+
private activityDebounceTimer;
|
|
26
|
+
/** State */
|
|
27
|
+
private running;
|
|
28
|
+
private warningActive;
|
|
29
|
+
private secondsRemaining;
|
|
30
|
+
/** Cross-tab activity sync */
|
|
31
|
+
private broadcastChannel;
|
|
32
|
+
/** Bound handlers for event listener cleanup. */
|
|
33
|
+
private boundOnActivity;
|
|
34
|
+
private boundOnVisibilityChange;
|
|
35
|
+
private boundOnBroadcastMessage;
|
|
36
|
+
start(config: Partial<SessionConfig> | undefined, callbacks: SessionCallbacks): void;
|
|
37
|
+
stop(): void;
|
|
38
|
+
continueSession(): void;
|
|
39
|
+
isWarningActive(): boolean;
|
|
40
|
+
getSecondsRemaining(): number;
|
|
41
|
+
private onActivity;
|
|
42
|
+
private onVisibilityChange;
|
|
43
|
+
/** Single source of truth for marking activity locally. */
|
|
44
|
+
private registerActivity;
|
|
45
|
+
private checkIdle;
|
|
46
|
+
private startWarning;
|
|
47
|
+
private startHeartbeat;
|
|
48
|
+
private fireHeartbeat;
|
|
49
|
+
private setupBroadcastChannel;
|
|
50
|
+
private teardownBroadcastChannel;
|
|
51
|
+
private broadcast;
|
|
52
|
+
private onBroadcastMessage;
|
|
53
|
+
private clearTimer;
|
|
54
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SessionManager — Central session lifecycle manager.
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* 1. Track user activity (mousemove, keydown, click, scroll, touch)
|
|
7
|
+
* 2. Send heartbeat at configurable intervals — only when user has been active
|
|
8
|
+
* 3. Show warning popup after idle timeout
|
|
9
|
+
* 4. Auto-logout after warning countdown expires
|
|
10
|
+
* 5. Sync activity across same-origin tabs via BroadcastChannel so one
|
|
11
|
+
* active tab keeps other tabs' sessions alive (no cascading logout).
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.SessionManager = void 0;
|
|
15
|
+
const sessionTypes_1 = require("./sessionTypes");
|
|
16
|
+
/** Events that indicate user activity. */
|
|
17
|
+
const ACTIVITY_EVENTS = [
|
|
18
|
+
'mousemove',
|
|
19
|
+
'mousedown',
|
|
20
|
+
'keydown',
|
|
21
|
+
'scroll',
|
|
22
|
+
'touchstart',
|
|
23
|
+
'click',
|
|
24
|
+
];
|
|
25
|
+
/** Debounce interval for activity events (ms). */
|
|
26
|
+
const ACTIVITY_DEBOUNCE_MS = 1000;
|
|
27
|
+
/** Name of the BroadcastChannel used for cross-tab activity sync. */
|
|
28
|
+
const BROADCAST_CHANNEL_NAME = 'zeni-session-activity';
|
|
29
|
+
class SessionManager {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.config = sessionTypes_1.DEFAULT_SESSION_CONFIG;
|
|
32
|
+
this.callbacks = null;
|
|
33
|
+
/** Timestamps and timers */
|
|
34
|
+
this.lastActivityTime = 0;
|
|
35
|
+
/** Timestamp of the last heartbeat fire (initial/periodic/continue/activity). */
|
|
36
|
+
this.lastHeartbeatTime = 0;
|
|
37
|
+
/** Set to true whenever user (or any same-origin tab) shows activity. */
|
|
38
|
+
this.hadActivitySinceLastHeartbeat = false;
|
|
39
|
+
this.idleCheckInterval = null;
|
|
40
|
+
this.heartbeatInterval = null;
|
|
41
|
+
this.countdownInterval = null;
|
|
42
|
+
this.activityDebounceTimer = null;
|
|
43
|
+
/** State */
|
|
44
|
+
this.running = false;
|
|
45
|
+
this.warningActive = false;
|
|
46
|
+
this.secondsRemaining = 0;
|
|
47
|
+
/** Cross-tab activity sync */
|
|
48
|
+
this.broadcastChannel = null;
|
|
49
|
+
/** Bound handlers for event listener cleanup. */
|
|
50
|
+
this.boundOnActivity = this.onActivity.bind(this);
|
|
51
|
+
this.boundOnVisibilityChange = this.onVisibilityChange.bind(this);
|
|
52
|
+
this.boundOnBroadcastMessage = this.onBroadcastMessage.bind(this);
|
|
53
|
+
}
|
|
54
|
+
// ─── Public API ────────────────────────────────────────────
|
|
55
|
+
start(config = {}, callbacks) {
|
|
56
|
+
if (this.running) {
|
|
57
|
+
this.stop();
|
|
58
|
+
}
|
|
59
|
+
this.config = { ...sessionTypes_1.DEFAULT_SESSION_CONFIG, ...config };
|
|
60
|
+
this.callbacks = callbacks;
|
|
61
|
+
this.running = true;
|
|
62
|
+
this.warningActive = false;
|
|
63
|
+
this.lastActivityTime = Date.now();
|
|
64
|
+
this.lastHeartbeatTime = 0; // fireHeartbeat('initial') below will set this
|
|
65
|
+
this.hadActivitySinceLastHeartbeat = true; // initial heartbeat is expected
|
|
66
|
+
console.log('[SessionManager] start() called with config:', this.config);
|
|
67
|
+
const heartbeatEnabled = this.config.heartbeatIntervalMinutes > 0;
|
|
68
|
+
const idleTrackingEnabled = this.config.isAutoLogoutEnabled;
|
|
69
|
+
if (!heartbeatEnabled && !idleTrackingEnabled) {
|
|
70
|
+
console.log('[SessionManager] Heartbeat and idle tracking both disabled — nothing to start');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Activity listeners — needed for both heartbeat activity-gate and idle
|
|
74
|
+
// detection. BroadcastChannel keeps multiple tabs in sync.
|
|
75
|
+
ACTIVITY_EVENTS.forEach((event) => {
|
|
76
|
+
document.addEventListener(event, this.boundOnActivity, {
|
|
77
|
+
capture: true,
|
|
78
|
+
passive: true,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
this.setupBroadcastChannel();
|
|
82
|
+
if (heartbeatEnabled) {
|
|
83
|
+
this.startHeartbeat();
|
|
84
|
+
// Initial heartbeat on sign-in
|
|
85
|
+
this.fireHeartbeat('initial');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log('[SessionManager] Heartbeat disabled (heartbeatIntervalMinutes <= 0)');
|
|
89
|
+
}
|
|
90
|
+
if (idleTrackingEnabled) {
|
|
91
|
+
document.addEventListener('visibilitychange', this.boundOnVisibilityChange);
|
|
92
|
+
this.idleCheckInterval = setInterval(() => this.checkIdle(), 1000);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log('[SessionManager] Idle tracking disabled for this user — heartbeat-only mode');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
stop() {
|
|
99
|
+
this.running = false;
|
|
100
|
+
this.warningActive = false;
|
|
101
|
+
ACTIVITY_EVENTS.forEach((event) => {
|
|
102
|
+
document.removeEventListener(event, this.boundOnActivity, {
|
|
103
|
+
capture: true,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
document.removeEventListener('visibilitychange', this.boundOnVisibilityChange);
|
|
107
|
+
this.teardownBroadcastChannel();
|
|
108
|
+
this.clearTimer('idleCheckInterval');
|
|
109
|
+
this.clearTimer('heartbeatInterval');
|
|
110
|
+
this.clearTimer('countdownInterval');
|
|
111
|
+
this.clearTimer('activityDebounceTimer');
|
|
112
|
+
}
|
|
113
|
+
continueSession() {
|
|
114
|
+
if (!this.running) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.warningActive = false;
|
|
118
|
+
this.secondsRemaining = 0;
|
|
119
|
+
this.lastActivityTime = Date.now();
|
|
120
|
+
this.hadActivitySinceLastHeartbeat = true;
|
|
121
|
+
this.clearTimer('countdownInterval');
|
|
122
|
+
// Fire heartbeat immediately — user explicitly extended the session
|
|
123
|
+
this.fireHeartbeat('continue-session');
|
|
124
|
+
this.callbacks?.onSessionExtended();
|
|
125
|
+
this.broadcast({ type: 'activity', timestamp: Date.now() });
|
|
126
|
+
this.startHeartbeat();
|
|
127
|
+
}
|
|
128
|
+
isWarningActive() {
|
|
129
|
+
return this.warningActive;
|
|
130
|
+
}
|
|
131
|
+
getSecondsRemaining() {
|
|
132
|
+
return this.secondsRemaining;
|
|
133
|
+
}
|
|
134
|
+
// ─── Private ───────────────────────────────────────────────
|
|
135
|
+
onActivity() {
|
|
136
|
+
if (!this.running || this.warningActive) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Debounce: only update lastActivityTime at most once per second
|
|
140
|
+
if (this.activityDebounceTimer != null) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
this.registerActivity(now);
|
|
145
|
+
this.broadcast({ type: 'activity', timestamp: now });
|
|
146
|
+
// If heartbeat is enabled and the interval has elapsed since the last
|
|
147
|
+
// heartbeat, fire one immediately and restart the timer. This covers
|
|
148
|
+
// the "user active again after >interval idle" case without waiting
|
|
149
|
+
// for the next scheduled tick.
|
|
150
|
+
if (this.config.heartbeatIntervalMinutes > 0) {
|
|
151
|
+
const intervalMs = this.config.heartbeatIntervalMinutes * 60 * 1000;
|
|
152
|
+
const timeSinceLastHeartbeat = now - this.lastHeartbeatTime;
|
|
153
|
+
if (timeSinceLastHeartbeat >= intervalMs) {
|
|
154
|
+
this.fireHeartbeat('activity-after-idle');
|
|
155
|
+
this.startHeartbeat();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
this.activityDebounceTimer = setTimeout(() => {
|
|
159
|
+
this.activityDebounceTimer = null;
|
|
160
|
+
}, ACTIVITY_DEBOUNCE_MS);
|
|
161
|
+
}
|
|
162
|
+
onVisibilityChange() {
|
|
163
|
+
if (!this.running) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Industry-standard idle detection: resetting on tab focus is expected.
|
|
167
|
+
if (document.visibilityState === 'visible') {
|
|
168
|
+
this.registerActivity(Date.now());
|
|
169
|
+
this.checkIdle();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Single source of truth for marking activity locally. */
|
|
173
|
+
registerActivity(timestamp) {
|
|
174
|
+
this.lastActivityTime = Math.max(this.lastActivityTime, timestamp);
|
|
175
|
+
this.hadActivitySinceLastHeartbeat = true;
|
|
176
|
+
}
|
|
177
|
+
checkIdle() {
|
|
178
|
+
if (!this.running || this.warningActive) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const idleMs = Date.now() - this.lastActivityTime;
|
|
182
|
+
const idleTimeoutMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
183
|
+
if (idleMs >= idleTimeoutMs) {
|
|
184
|
+
this.startWarning();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
startWarning() {
|
|
188
|
+
this.warningActive = true;
|
|
189
|
+
this.secondsRemaining = this.config.warningDurationSeconds;
|
|
190
|
+
// Don't extend session while warning is showing.
|
|
191
|
+
this.clearTimer('heartbeatInterval');
|
|
192
|
+
this.callbacks?.onWarningStart(this.secondsRemaining);
|
|
193
|
+
this.countdownInterval = setInterval(() => {
|
|
194
|
+
this.secondsRemaining -= 1;
|
|
195
|
+
if (this.secondsRemaining <= 0) {
|
|
196
|
+
this.clearTimer('countdownInterval');
|
|
197
|
+
this.warningActive = false;
|
|
198
|
+
this.callbacks?.onAutoLogout();
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this.callbacks?.onWarningTick(this.secondsRemaining);
|
|
202
|
+
}
|
|
203
|
+
}, 1000);
|
|
204
|
+
}
|
|
205
|
+
startHeartbeat() {
|
|
206
|
+
this.clearTimer('heartbeatInterval');
|
|
207
|
+
const intervalMs = this.config.heartbeatIntervalMinutes * 60 * 1000;
|
|
208
|
+
console.log(`[SessionManager] Heartbeat timer set to fire every ${this.config.heartbeatIntervalMinutes} min (${intervalMs} ms)`);
|
|
209
|
+
this.heartbeatInterval = setInterval(() => {
|
|
210
|
+
if (!this.running || this.warningActive) {
|
|
211
|
+
console.log('[SessionManager] Heartbeat tick skipped — running:', this.running, 'warningActive:', this.warningActive);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!this.hadActivitySinceLastHeartbeat) {
|
|
215
|
+
console.log('[SessionManager] Heartbeat tick skipped — no activity in this interval');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.fireHeartbeat('periodic');
|
|
219
|
+
}, intervalMs);
|
|
220
|
+
}
|
|
221
|
+
fireHeartbeat(reason) {
|
|
222
|
+
console.log(`[SessionManager] Heartbeat fired (${reason})`);
|
|
223
|
+
this.lastHeartbeatTime = Date.now();
|
|
224
|
+
this.hadActivitySinceLastHeartbeat = false;
|
|
225
|
+
this.callbacks?.onHeartbeat();
|
|
226
|
+
// Let other tabs know we just fired a heartbeat — they can skip theirs
|
|
227
|
+
// this cycle to avoid N tabs all firing heartbeats.
|
|
228
|
+
this.broadcast({ type: 'heartbeat-fired', timestamp: this.lastHeartbeatTime });
|
|
229
|
+
}
|
|
230
|
+
// ─── BroadcastChannel (cross-tab activity sync) ───────────
|
|
231
|
+
setupBroadcastChannel() {
|
|
232
|
+
if (typeof BroadcastChannel === 'undefined') {
|
|
233
|
+
console.log('[SessionManager] BroadcastChannel not available — cross-tab sync disabled');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
this.broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
|
|
238
|
+
this.broadcastChannel.addEventListener('message', this.boundOnBroadcastMessage);
|
|
239
|
+
console.log('[SessionManager] BroadcastChannel ready for cross-tab sync');
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
console.warn('[SessionManager] BroadcastChannel setup failed:', err);
|
|
243
|
+
this.broadcastChannel = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
teardownBroadcastChannel() {
|
|
247
|
+
if (this.broadcastChannel == null) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.broadcastChannel.removeEventListener('message', this.boundOnBroadcastMessage);
|
|
251
|
+
this.broadcastChannel.close();
|
|
252
|
+
this.broadcastChannel = null;
|
|
253
|
+
}
|
|
254
|
+
broadcast(message) {
|
|
255
|
+
this.broadcastChannel?.postMessage(message);
|
|
256
|
+
}
|
|
257
|
+
onBroadcastMessage(event) {
|
|
258
|
+
if (!this.running) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const msg = event.data;
|
|
262
|
+
if (msg == null) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (msg.type === 'activity') {
|
|
266
|
+
// Another tab saw user activity — treat as activity here too.
|
|
267
|
+
this.registerActivity(msg.timestamp);
|
|
268
|
+
}
|
|
269
|
+
else if (msg.type === 'heartbeat-fired') {
|
|
270
|
+
// Another tab already fired a heartbeat in this interval — backend
|
|
271
|
+
// session is already extended. Reset our local tracking so we don't
|
|
272
|
+
// also fire one this cycle.
|
|
273
|
+
this.lastHeartbeatTime = Math.max(this.lastHeartbeatTime, msg.timestamp);
|
|
274
|
+
this.hadActivitySinceLastHeartbeat = false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
clearTimer(name) {
|
|
278
|
+
const timer = this[name];
|
|
279
|
+
if (timer != null) {
|
|
280
|
+
if (name === 'activityDebounceTimer') {
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
clearInterval(timer);
|
|
285
|
+
}
|
|
286
|
+
this[name] = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
exports.SessionManager = SessionManager;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
+
import { RootState } from '../../../reducer';
|
|
3
|
+
import { ZeniAPI } from '../../../zeniAPI';
|
|
4
|
+
import { sendSessionHeartbeat, sessionHeartbeatFailure, sessionHeartbeatSuccess } from '../tenantReducer';
|
|
5
|
+
export type ActionType = ReturnType<typeof sendSessionHeartbeat> | ReturnType<typeof sessionHeartbeatSuccess> | ReturnType<typeof sessionHeartbeatFailure>;
|
|
6
|
+
export declare const sessionHeartbeatEpic: (actions$: ActionsObservable<ActionType>, _: StateObservable<RootState>, zeniAPI: ZeniAPI) => import("rxjs").Observable<{
|
|
7
|
+
payload: {
|
|
8
|
+
expiresAt: string;
|
|
9
|
+
};
|
|
10
|
+
type: "tenant/sessionHeartbeatSuccess";
|
|
11
|
+
} | {
|
|
12
|
+
payload: {
|
|
13
|
+
error: import("../../../responsePayload").ZeniAPIStatus<Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
type: "tenant/sessionHeartbeatFailure";
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sessionHeartbeatEpic = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const operators_1 = require("rxjs/operators");
|
|
6
|
+
const responsePayload_1 = require("../../../responsePayload");
|
|
7
|
+
const tenantReducer_1 = require("../tenantReducer");
|
|
8
|
+
const sessionHeartbeatEpic = (actions$, _, zeniAPI) => actions$.pipe((0, operators_1.filter)(tenantReducer_1.sendSessionHeartbeat.match), (0, operators_1.switchMap)(() => zeniAPI
|
|
9
|
+
.postAndGetJSON(`${zeniAPI.apiEndPoints.authMicroServiceBaseUrl}/1.0/heartbeat`)
|
|
10
|
+
.pipe((0, operators_1.mergeMap)((response) => {
|
|
11
|
+
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
12
|
+
return (0, rxjs_1.of)((0, tenantReducer_1.sessionHeartbeatSuccess)(response.data?.expiry ?? ''));
|
|
13
|
+
}
|
|
14
|
+
return (0, rxjs_1.of)((0, tenantReducer_1.sessionHeartbeatFailure)(response.status));
|
|
15
|
+
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, tenantReducer_1.sessionHeartbeatFailure)((0, responsePayload_1.createZeniAPIStatus)('Heartbeat failed', 'Session heartbeat API call errored: ' + JSON.stringify(error))))))));
|
|
16
|
+
exports.sessionHeartbeatEpic = sessionHeartbeatEpic;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session management types for idle timeout and heartbeat.
|
|
3
|
+
*/
|
|
4
|
+
export interface SessionConfig {
|
|
5
|
+
/** Minutes between heartbeat API calls while user is active. Default: 10 */
|
|
6
|
+
heartbeatIntervalMinutes: number;
|
|
7
|
+
/** Minutes of inactivity before the warning popup appears. Default: 30 */
|
|
8
|
+
idleTimeoutMinutes: number;
|
|
9
|
+
/** Whether auto-logout is enabled. Default: true */
|
|
10
|
+
isAutoLogoutEnabled: boolean;
|
|
11
|
+
/** Seconds to count down in the warning popup before auto-logout. Default: 60 */
|
|
12
|
+
warningDurationSeconds: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_SESSION_CONFIG: SessionConfig;
|
|
15
|
+
export interface SessionCallbacks {
|
|
16
|
+
/** Called when the countdown reaches zero — app should sign out. */
|
|
17
|
+
onAutoLogout: () => void;
|
|
18
|
+
/** Called to send a heartbeat API request. */
|
|
19
|
+
onHeartbeat: () => void;
|
|
20
|
+
/** Called when the session is extended (heartbeat success or "Continue Session"). */
|
|
21
|
+
onSessionExtended: () => void;
|
|
22
|
+
/** Called when the warning countdown starts. */
|
|
23
|
+
onWarningStart: (secondsRemaining: number) => void;
|
|
24
|
+
/** Called every second during the countdown. */
|
|
25
|
+
onWarningTick: (secondsRemaining: number) => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session management types for idle timeout and heartbeat.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_SESSION_CONFIG = void 0;
|
|
7
|
+
exports.DEFAULT_SESSION_CONFIG = {
|
|
8
|
+
heartbeatIntervalMinutes: 10,
|
|
9
|
+
idleTimeoutMinutes: 30,
|
|
10
|
+
isAutoLogoutEnabled: true,
|
|
11
|
+
warningDurationSeconds: 60,
|
|
12
|
+
};
|
|
@@ -51,7 +51,11 @@ export declare const updateTenants: import("@reduxjs/toolkit").ActionCreatorWith
|
|
|
51
51
|
accountLockTime?: string;
|
|
52
52
|
accountUnlockTime?: string;
|
|
53
53
|
isAccountLocked?: boolean;
|
|
54
|
-
}, "tenant/sendEmailMagicLinkToUserFailure">, updateSignInState: import("@reduxjs/toolkit").ActionCreatorWithPayload<FetchState, "tenant/updateSignInState">, doSignOut: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"tenant/doSignOut">, signOutSuccess: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"tenant/signOutSuccess">,
|
|
54
|
+
}, "tenant/sendEmailMagicLinkToUserFailure">, updateSignInState: import("@reduxjs/toolkit").ActionCreatorWithPayload<FetchState, "tenant/updateSignInState">, doSignOut: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"tenant/doSignOut">, signOutSuccess: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"tenant/signOutSuccess">, sendSessionHeartbeat: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"tenant/sendSessionHeartbeat">, sessionHeartbeatSuccess: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[expiresAt: string], {
|
|
55
|
+
expiresAt: string;
|
|
56
|
+
}, "tenant/sessionHeartbeatSuccess", never, never>, sessionHeartbeatFailure: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[error: ZeniAPIStatus<Record<string, unknown>>], {
|
|
57
|
+
error: ZeniAPIStatus<Record<string, unknown>>;
|
|
58
|
+
}, "tenant/sessionHeartbeatFailure", never, never>, updateLoggedInUser: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[userPayload: LoggedInUserPayload, zeniSessionId: string, authProvider: AuthProvider], {
|
|
55
59
|
userPayload: LoggedInUserPayload;
|
|
56
60
|
zeniSessionId: string;
|
|
57
61
|
authProvider: AuthProvider;
|