|
16766
|
748
|
11
|
2026-05-11T09:22:47.023042+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491367023_m1.jpg...
|
Finder
|
Copy
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Copying “CleanShot 2026-05-11 at 12.17.26.mp4” to Copying “CleanShot 2026-05-11 at 12.17.26.mp4” to “2026”
stop progress
67,1 MB of 144,5 MB - Estimating time remaining…...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Copying “CleanShot 2026-05-11 at 12.17.26.mp4” to “2026”","depth":2,"bounds":{"left":0.5416667,"top":0.27222222,"width":0.21736111,"height":0.015555556},"on_screen":true,"automation_id":"_NS:59","role_description":"text"},{"role":"AXButton","text":"stop progress","depth":2,"bounds":{"left":0.76319444,"top":0.28833333,"width":0.009027778,"height":0.014444444},"on_screen":true,"automation_id":"_NS:8","role_description":"button","is_enabled":true,"is_focused":true},{"role":"AXStaticText","text":"67,1 MB of 144,5 MB - Estimating time remaining…","depth":2,"bounds":{"left":0.5416667,"top":0.30444443,"width":0.21736111,"height":0.015555556},"on_screen":true,"automation_id":"_NS:89","role_description":"text"}]...
|
-9195470564954161022
|
-7761165428826560482
|
visual_change
|
accessibility
|
NULL
|
Copying “CleanShot 2026-05-11 at 12.17.26.mp4” to Copying “CleanShot 2026-05-11 at 12.17.26.mp4” to “2026”
stop progress
67,1 MB of 144,5 MB - Estimating time remaining…...
|
16763
|
NULL
|
NULL
|
NULL
|
|
16767
|
748
|
12
|
2026-05-11T09:22:50.054639+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491370054_m1.jpg...
|
Finder
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFileEditViewGoWindowHelplalolj Support Daily FinderFileEditViewGoWindowHelplalolj Support Daily • in 2h 38 mDEV (docker)• жзDOCKERO ₴1DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY] laysJY-20698-fix-SF-activity-types-on-new-playbookJY-20543-AJ-report-trackingJY-20384-handle-auto-sync-with-no-access-to-event-typeJY-20458-ask-Jiminny-user-definitionsJY-19666-fix-import-contacts-account-associationJY-19666-HS-import-contacts-and-accounts-batch-jobJY-20458-Ask-Jiminny-ReportsJY-20200-batch-update-CRM-objects-SalesforceJY-19666-HS-webhooks-add-contact-and-companyJY-20348-trigger-setup-DI-layout-on-team-creationJY-20326-refactor-info-message-in-commandJY-20317-fix-auto-log-delay-issue-on-all-channels-disabledJY-20312-remove-on-update-change-last-synced-at-crm-configurationsJY-20306-SF-skip-auto-sync-for-task-based-playbookJY-20192-remove-deleted-team-from-saved-search-filtersJY-20197-import-opportunity-batch-jobJY-20293-enable-status-field-for-pipedrive-dealsJY-20191-remove-commands-interactive-promptsJY-20118-change-default-sync-strategyJY-20183-add-cache-on-auto-log-delayJY-20197-add-import-opportunity-batch-job20118-hs-opportunity-make-webhook-strategy-defaultJY-20118-make-default-hs-opportunity-sync-strategy-webhook-basedJY-20196-handle-opportunity-without-noteJY-20118-improve-opportunity-importJY-20189-handle-activity-search-on-deleted-groupsJY-20160JY-20145-filter-out-converted-leads-when-matchingJY-20150-skip-push-summary-on-summary-ready-1f-autologJY-20132-fix-note-encodingJY-19792-clean-logslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]-zsh84-zsh885100% C8• Mon 11 May 12:22:49L₴81screenpipe"0 ₴6DEV...
|
NULL
|
5795168123965141546
|
NULL
|
visual_change
|
ocr
|
NULL
|
FinderFileEditViewGoWindowHelplalolj Support Daily FinderFileEditViewGoWindowHelplalolj Support Daily • in 2h 38 mDEV (docker)• жзDOCKERO ₴1DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY] laysJY-20698-fix-SF-activity-types-on-new-playbookJY-20543-AJ-report-trackingJY-20384-handle-auto-sync-with-no-access-to-event-typeJY-20458-ask-Jiminny-user-definitionsJY-19666-fix-import-contacts-account-associationJY-19666-HS-import-contacts-and-accounts-batch-jobJY-20458-Ask-Jiminny-ReportsJY-20200-batch-update-CRM-objects-SalesforceJY-19666-HS-webhooks-add-contact-and-companyJY-20348-trigger-setup-DI-layout-on-team-creationJY-20326-refactor-info-message-in-commandJY-20317-fix-auto-log-delay-issue-on-all-channels-disabledJY-20312-remove-on-update-change-last-synced-at-crm-configurationsJY-20306-SF-skip-auto-sync-for-task-based-playbookJY-20192-remove-deleted-team-from-saved-search-filtersJY-20197-import-opportunity-batch-jobJY-20293-enable-status-field-for-pipedrive-dealsJY-20191-remove-commands-interactive-promptsJY-20118-change-default-sync-strategyJY-20183-add-cache-on-auto-log-delayJY-20197-add-import-opportunity-batch-job20118-hs-opportunity-make-webhook-strategy-defaultJY-20118-make-default-hs-opportunity-sync-strategy-webhook-basedJY-20196-handle-opportunity-without-noteJY-20118-improve-opportunity-importJY-20189-handle-activity-search-on-deleted-groupsJY-20160JY-20145-filter-out-converted-leads-when-matchingJY-20150-skip-push-summary-on-summary-ready-1f-autologJY-20132-fix-note-encodingJY-19792-clean-logslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]-zsh84-zsh885100% C8• Mon 11 May 12:22:49L₴81screenpipe"0 ₴6DEV...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16768
|
748
|
13
|
2026-05-11T09:22:50.967346+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491370967_m1.jpg...
|
Finder
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFileEditViewGoWindowHelplalolj Support Daily FinderFileEditViewGoWindowHelplalolj Support Daily • in 2h 38 mDEV (docker)• жзDOCKERO ₴1DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY] laysJY-20698-fix-SF-activity-types-on-new-playbookJY-20543-AJ-report-trackingJY-20384-handle-auto-sync-with-no-access-to-event-typeJY-20458-ask-Jiminny-user-definitionsJY-19666-fix-import-contacts-account-associationJY-19666-HS-import-contacts-and-accounts-batch-jobJY-20458-Ask-Jiminny-ReportsJY-20200-batch-update-CRM-objects-SalesforceJY-19666-HS-webhooks-add-contact-and-companyJY-20348-trigger-setup-DI-layout-on-team-creationJY-20326-refactor-info-message-in-commandJY-20317-fix-auto-log-delay-issue-on-all-channels-disabledJY-20312-remove-on-update-change-last-synced-at-crm-configurationsJY-20306-SF-skip-auto-sync-for-task-based-playbookJY-20192-remove-deleted-team-from-saved-search-filtersJY-20197-import-opportunity-batch-jobJY-20293-enable-status-field-for-pipedrive-dealsJY-20191-remove-commands-interactive-promptsJY-20118-change-default-sync-strategyJY-20183-add-cache-on-auto-log-delayJY-20197-add-import-opportunity-batch-job20118-hs-opportunity-make-webhook-strategy-defaultJY-20118-make-default-hs-opportunity-sync-strategy-webhook-basedJY-20196-handle-opportunity-without-noteJY-20118-improve-opportunity-importJY-20189-handle-activity-search-on-deleted-groupsJY-20160JY-20145-filter-out-converted-leads-when-matchingJY-20150-skip-push-summary-on-summary-ready-1f-autologJY-20132-fix-note-encodingJY-19792-clean-logslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]-zsh84-zsh885100% C8• Mon 11 May 12:22:50L₴81screenpipe"886DEV...
|
NULL
|
3999535913005802134
|
NULL
|
click
|
ocr
|
NULL
|
FinderFileEditViewGoWindowHelplalolj Support Daily FinderFileEditViewGoWindowHelplalolj Support Daily • in 2h 38 mDEV (docker)• жзDOCKERO ₴1DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY] laysJY-20698-fix-SF-activity-types-on-new-playbookJY-20543-AJ-report-trackingJY-20384-handle-auto-sync-with-no-access-to-event-typeJY-20458-ask-Jiminny-user-definitionsJY-19666-fix-import-contacts-account-associationJY-19666-HS-import-contacts-and-accounts-batch-jobJY-20458-Ask-Jiminny-ReportsJY-20200-batch-update-CRM-objects-SalesforceJY-19666-HS-webhooks-add-contact-and-companyJY-20348-trigger-setup-DI-layout-on-team-creationJY-20326-refactor-info-message-in-commandJY-20317-fix-auto-log-delay-issue-on-all-channels-disabledJY-20312-remove-on-update-change-last-synced-at-crm-configurationsJY-20306-SF-skip-auto-sync-for-task-based-playbookJY-20192-remove-deleted-team-from-saved-search-filtersJY-20197-import-opportunity-batch-jobJY-20293-enable-status-field-for-pipedrive-dealsJY-20191-remove-commands-interactive-promptsJY-20118-change-default-sync-strategyJY-20183-add-cache-on-auto-log-delayJY-20197-add-import-opportunity-batch-job20118-hs-opportunity-make-webhook-strategy-defaultJY-20118-make-default-hs-opportunity-sync-strategy-webhook-basedJY-20196-handle-opportunity-without-noteJY-20118-improve-opportunity-importJY-20189-handle-activity-search-on-deleted-groupsJY-20160JY-20145-filter-out-converted-leads-when-matchingJY-20150-skip-push-summary-on-summary-ready-1f-autologJY-20132-fix-note-encodingJY-19792-clean-logslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]-zsh84-zsh885100% C8• Mon 11 May 12:22:50L₴81screenpipe"886DEV...
|
16767
|
NULL
|
NULL
|
NULL
|
|
16769
|
749
|
14
|
2026-05-11T09:22:50.954214+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491370954_m2.jpg...
|
Finder
|
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFavouritesjiminny(* AirDrop@ RecentsA Applic FinderFavouritesjiminny(* AirDrop@ RecentsA Applications|9 Documentsii lukasIcloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 NetworkAAр!.• Orange• Red• Yellow• Greer• Blue• Purple• All lags..VIeWWinaowscreenpipearchive.db• #recycledb.sqlite-shmdb.sqliteVOOSsync.log• screenpipe.2026-05-07.0.1ogv data•2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24• 2026-04-22•2026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14- screenpipe_sync_updated.sharchive.db-oak>?app• db.sqlite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesShare Edit Tagsv Q SearchDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterdav at 14:45Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Aar 2026 at 22:2228 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Anr 2026 nt 0:1617 Apr 2026 at 8:5716 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:312A Anr 2026 20:10.26 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51v Size12,92 GB Document62,68 GBFolder33 KB Document2.37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder1667 MP Calder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolderA50 9 MP Soldon83/.2ME215 GRIrolderFolden1,09 GB20 KBTerminal scripts11.13 GB193 KB FolderZero bytesDocument15 KBTerminal scripts21 butes ISONIZero bytes13 KB Folder•1 4m 55s1AA5 MR30 items, 1.94 TB availabld•• cFavourites• jiminny(®) AirDrop• Recents* Applications|9 Documents• Downloadsii lukasiCloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 Networ!• CRM• Orange• Red• Yellow• Greero Bue• Purple•) All lags..workv 2026CleanShot 2026-05-11 at 12.17.26.mp4= Dally 2026-05-11.mp4a Dailv 2026-05-08 mo41-1 2026-05-07.mp4i Daily 2026-05-07.mp4шгя 1-1 2026-04-24,mo4• Daily 2026-04-24.mp4ms User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4** Refinement 2026-04-06.mp4Daily 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4R Daily 2026-04-17.mp4ru Daily 2026-04-16.mp4# Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4- Daily 2026-04-09.mp4wa Daily 2026-04-08.mp4• Dailv 2026-04-07 mo4Daily 2026-04-06.mp4- Daily 2026-04-03.mp4las Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4• Dailv 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4am Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/a Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/_: Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.2s. Daily 2026-03-11.mp4• Daily 2026-03-10 mn/xe: Refinement 2026-03-09.mp4Ra Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4H Dlonnina 2096 л9 Ли mnлsupoont Dally • In zn 3omvQ SearchDate ModifiedToday at 12:22Today at 12:22Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Anr 2026 at 10:2120 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Anr 2026 at 11:1414 Apr 2026 at 10:09O Anr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:01A Anr 2026 at 10:091 Apr 2026 at 12-2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 10:0610 Mar 206 ct 19:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:06aMer nand nt 47:046 Mar 2026 at 9:57Aot ACAcalaatad A 0A TO aucilahl)Mon 11 May 12-22-o0491,3 MBMPEG-4 movie1.37 GE1,55 GBMPEG-4 movieMPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie126 GRIMDEG-A movid2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513,4 MB2,75 GB1,44 GB924,4 MBMPEG-4 movieMDEG.A movidMPEG-4 movie262 6 MPMPEG-A movid748.8 MBMPEG-4 movie1,04 GBMPEG-4 movie575.5 M:720 6 MR1,02 GB4,68 GBMDEG.A movidMPEG-4 movieMDEG-A movie2AGP923,6 MBMPEG-4 movie2,77 GB641.8 MEMPEG-4 movie99/2MR476,6 MB550.8 M:MDEC.A movidMPEG-4 movie2AAGPMDEG-A movid438,9 MBMPEG-4 movie1,68 GB430.4 MEMPEG-4 movieMPEG-4 movie2.38 GB2,26 GB386.3 M:MDEC.A movidMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GE1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB MPEG-4 movie1.02 GEMPEG-4 movie627 G MP978,7 MB798,7 MEMDEG.A movidMDEG-A movieMPEG-4 movieMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movienen cplMDEeA movit...
|
NULL
|
4497172969893734119
|
NULL
|
click
|
ocr
|
NULL
|
FinderFavouritesjiminny(* AirDrop@ RecentsA Applic FinderFavouritesjiminny(* AirDrop@ RecentsA Applications|9 Documentsii lukasIcloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 NetworkAAр!.• Orange• Red• Yellow• Greer• Blue• Purple• All lags..VIeWWinaowscreenpipearchive.db• #recycledb.sqlite-shmdb.sqliteVOOSsync.log• screenpipe.2026-05-07.0.1ogv data•2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24• 2026-04-22•2026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14- screenpipe_sync_updated.sharchive.db-oak>?app• db.sqlite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesShare Edit Tagsv Q SearchDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterdav at 14:45Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Aar 2026 at 22:2228 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Anr 2026 nt 0:1617 Apr 2026 at 8:5716 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:312A Anr 2026 20:10.26 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51v Size12,92 GB Document62,68 GBFolder33 KB Document2.37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder1667 MP Calder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolderA50 9 MP Soldon83/.2ME215 GRIrolderFolden1,09 GB20 KBTerminal scripts11.13 GB193 KB FolderZero bytesDocument15 KBTerminal scripts21 butes ISONIZero bytes13 KB Folder•1 4m 55s1AA5 MR30 items, 1.94 TB availabld•• cFavourites• jiminny(®) AirDrop• Recents* Applications|9 Documents• Downloadsii lukasiCloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 Networ!• CRM• Orange• Red• Yellow• Greero Bue• Purple•) All lags..workv 2026CleanShot 2026-05-11 at 12.17.26.mp4= Dally 2026-05-11.mp4a Dailv 2026-05-08 mo41-1 2026-05-07.mp4i Daily 2026-05-07.mp4шгя 1-1 2026-04-24,mo4• Daily 2026-04-24.mp4ms User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4** Refinement 2026-04-06.mp4Daily 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4R Daily 2026-04-17.mp4ru Daily 2026-04-16.mp4# Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4- Daily 2026-04-09.mp4wa Daily 2026-04-08.mp4• Dailv 2026-04-07 mo4Daily 2026-04-06.mp4- Daily 2026-04-03.mp4las Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4• Dailv 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4am Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/a Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/_: Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.2s. Daily 2026-03-11.mp4• Daily 2026-03-10 mn/xe: Refinement 2026-03-09.mp4Ra Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4H Dlonnina 2096 л9 Ли mnлsupoont Dally • In zn 3omvQ SearchDate ModifiedToday at 12:22Today at 12:22Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Anr 2026 at 10:2120 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Anr 2026 at 11:1414 Apr 2026 at 10:09O Anr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:01A Anr 2026 at 10:091 Apr 2026 at 12-2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 10:0610 Mar 206 ct 19:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:06aMer nand nt 47:046 Mar 2026 at 9:57Aot ACAcalaatad A 0A TO aucilahl)Mon 11 May 12-22-o0491,3 MBMPEG-4 movie1.37 GE1,55 GBMPEG-4 movieMPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie126 GRIMDEG-A movid2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513,4 MB2,75 GB1,44 GB924,4 MBMPEG-4 movieMDEG.A movidMPEG-4 movie262 6 MPMPEG-A movid748.8 MBMPEG-4 movie1,04 GBMPEG-4 movie575.5 M:720 6 MR1,02 GB4,68 GBMDEG.A movidMPEG-4 movieMDEG-A movie2AGP923,6 MBMPEG-4 movie2,77 GB641.8 MEMPEG-4 movie99/2MR476,6 MB550.8 M:MDEC.A movidMPEG-4 movie2AAGPMDEG-A movid438,9 MBMPEG-4 movie1,68 GB430.4 MEMPEG-4 movieMPEG-4 movie2.38 GB2,26 GB386.3 M:MDEC.A movidMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GE1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB MPEG-4 movie1.02 GEMPEG-4 movie627 G MP978,7 MB798,7 MEMDEG.A movidMDEG-A movieMPEG-4 movieMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movienen cplMDEeA movit...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16770
|
748
|
14
|
2026-05-11T09:22:52.130436+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491372130_m1.jpg...
|
Finder
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFileEditViewGoWindowHelplalolj Support Daily FinderFileEditViewGoWindowHelplalolj Support Daily • in 2h 38 m100% C8• Mon 11 May 12:22:52L₴81DEV (docker)• жзDOCKERO ₴1DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY] laysJY-20698-fix-SF-activity-types-on-new-playbookJY-20543-AJ-report-trackingJY-20384-handle-auto-sync-with-no-access-to-event-typeJY-20458-ask-Jiminny-user-definitionsJY-19666-fix-import-contacts-account-associationJY-19666-HS-import-contacts-and-accounts-batch-jobJY-20458-Ask-Jiminny-ReportsJY-20200-batch-update-CRM-objects-SalesforceJY-19666-HS-webhooks-add-contact-and-companyJY-20348-trigger-setup-DI-layout-on-team-creationJY-20326-refactor-info-message-in-commandJY-20317-fix-auto-log-delay-issue-on-all-channels-disabledJY-20312-remove-on-update-change-last-synced-at-crm-configurationsJY-20306-SF-skip-auto-sync-for-task-based-playbookJY-20192-remove-deleted-team-from-saved-search-filtersJY-20197-import-opportunity-batch-jobJY-20293-enable-status-field-for-pipedrive-dealsJY-20191-remove-commands-interactive-promptsJY-20118-change-default-sync-strategyJY-20183-add-cache-on-auto-log-delayJY-20197-add-import-opportunity-batch-job20118-hs-opportunity-make-webhook-strategy-defaultJY-20118-make-default-hs-opportunity-sync-strategy-webhook-basedJY-20196-handle-opportunity-without-noteJY-20118-improve-opportunity-importJY-20189-handle-activity-search-on-deleted-groupsJY-20160JY-20145-filter-out-converted-leads-when-matchingJY-20150-skip-push-summary-on-summary-ready-1f-autologJY-20132-fix-note-encodingJY-19792-clean-logslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]-zsh84-zsh885screenpipe"DEV...
|
NULL
|
-5668835996874430462
|
NULL
|
click
|
ocr
|
NULL
|
FinderFileEditViewGoWindowHelplalolj Support Daily FinderFileEditViewGoWindowHelplalolj Support Daily • in 2h 38 m100% C8• Mon 11 May 12:22:52L₴81DEV (docker)• жзDOCKERO ₴1DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY] laysJY-20698-fix-SF-activity-types-on-new-playbookJY-20543-AJ-report-trackingJY-20384-handle-auto-sync-with-no-access-to-event-typeJY-20458-ask-Jiminny-user-definitionsJY-19666-fix-import-contacts-account-associationJY-19666-HS-import-contacts-and-accounts-batch-jobJY-20458-Ask-Jiminny-ReportsJY-20200-batch-update-CRM-objects-SalesforceJY-19666-HS-webhooks-add-contact-and-companyJY-20348-trigger-setup-DI-layout-on-team-creationJY-20326-refactor-info-message-in-commandJY-20317-fix-auto-log-delay-issue-on-all-channels-disabledJY-20312-remove-on-update-change-last-synced-at-crm-configurationsJY-20306-SF-skip-auto-sync-for-task-based-playbookJY-20192-remove-deleted-team-from-saved-search-filtersJY-20197-import-opportunity-batch-jobJY-20293-enable-status-field-for-pipedrive-dealsJY-20191-remove-commands-interactive-promptsJY-20118-change-default-sync-strategyJY-20183-add-cache-on-auto-log-delayJY-20197-add-import-opportunity-batch-job20118-hs-opportunity-make-webhook-strategy-defaultJY-20118-make-default-hs-opportunity-sync-strategy-webhook-basedJY-20196-handle-opportunity-without-noteJY-20118-improve-opportunity-importJY-20189-handle-activity-search-on-deleted-groupsJY-20160JY-20145-filter-out-converted-leads-when-matchingJY-20150-skip-push-summary-on-summary-ready-1f-autologJY-20132-fix-note-encodingJY-19792-clean-logslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]-zsh84-zsh885screenpipe"DEV...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16771
|
749
|
15
|
2026-05-11T09:22:52.626348+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491372626_m2.jpg...
|
Notion Calendar
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFavouritesE jiminny(* AirDropO Recents# Appl FinderFavouritesE jiminny(* AirDropO Recents# Applications© DocumentsG lukasIcloud• iCloud Drive992 Svnc toldeLocationsO DXP4800PLUS-B5F A® Network• CRM• Orange• Red• Yellow• Green• Blue• Purple• All lags..VIeWWinaowscreenpipearchive.db#recycledb.sqlite-shmdb.sqliteVOOSB sync.log• screenpipe.2026-05-07.0.1ogv data2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24>2026-04-222026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14• screenpipe_sync_updated.sharchive.db-oak>?appdb.salite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesShareEdit Tags~ (Q Search'Date ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterday at 14:45Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Apr 2026 at 22:2328 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Apr 2026 at 9:161/ Apr 2026 at 8.0/16 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:3126 Apr 2026 at 20:1026 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51•1 4m 55s1AA5 MR30 items, 1,94 TB available12,92 GB Document62,68 GBFolder33 KB Document2,37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder166,7 MB Folder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolder450,8 MB Folder83/.2MErolder215 GRIFolden1,09 GB20 KBTerminal scripts1113 G:193 KB FolderZero bytes15 KBTerminal scripts21 hutes ISONZero bytes13 KB Folder$088 =00FavouritesE jiminny(* AirDrop• RecentsA Applications© Documents© Downloadsii lukasiCloud• iCloud Drive283 Sync folderLocationsO DXP4800PLUS-B5F A® Network• CRM• Orange• Red• Yellow• Greer• Blue• Purple•) All lags..workv N 2026= Dally 2100:2026-05-11 at 12.1726.mр4- Daly 2993898. mрa1-12020008 107 81P4Daily 2026-05-07.mp4uw 1-12025604204.mo4* Daily 2 Rehatte- 24.mp4en User lPilcómmpredgccreanSh00 2028405-17a(12.17.26.mp4"Daily 2BUfpiRâte3.mp4Daily 2RRake An23.mp4u RefinBIRER{68,04-06.mp4- Daily 2026-04-21.mp4Da Refineroanv2026-04-20.mo4Daily 28Aêr®A-20.mp4R Daily 2026* Daly 2026-04-78. m: • • •Dlannint.2026.04.15 mn/lDaily 2Quick Aotioh$p4•Ucer- Daily 2icode Selected Video Fflesw: Daily 2New Term2 Tab HereDaily 2 New JTer7 indow HereI Daily 2028904-86.964y00xDaily 2026-04-03.mp4es Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4- Daily 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4sn Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4— Peview 2026-02-19 mn/a Planing 2026-03-18.mp4•ketro 2020-03-1/.mp4• Dailv 2026-03-17 mn/- Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.e Daily 2026-03-11.mp4• Daily 2026-03-10 mn/*: Refinement 2026-03-09.mp4• Daily 2026-03-09.mp4rn Dailv 2026-03-06.mo4sa: Plannina 2026-03-04.mD4Support Daily - in 2h 38 mQ SearchDate ModifiedToday at 12:22Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Apr 2026 at 10:2121 Apr 2026 at 11:0221 Apr 2026 at 10:0020 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Apr 2026 at 10:0015 Apr 2026 at 11:1414 Apr 2026 at 17:3714 Apr 2026 at 10:09OAnr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:016 Apr 2026 at 10:083 Apr 2026 at 10:211 Apr 2026 at 12:2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0926 Mar 2026 at 9:5924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0619 Mar 2026 at 12:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:1816 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:0610 Mar 2026 at 9:579 Mar 2026 at 17:046 Mar 2026 at 9:574 Mar 2026 at 11:091 of 154 selected, 1,94 TB availableMon 11 May 12:22:52-- Folder491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1,55 GBMPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MBMPEG-4 movie724 MB1.74 GEMPEG-4 movie1,36 GBMPEG-4 movie2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 MEMPEG-4 movie2,75 GBMPEG-4 movie1,44 CBMPEG-4 movie924,4 MBMPEG-4 movie262 6 MPMPEG-A movid748,8 MBMPEG-4 movie1,04 GB575.5 M:720,5 MBMPEG-4 movie1,02 GB4,68 GBMPEG-4 movie2AGPMDEG-A movie923,6 MBMPEG-4 movie2,77 GB641.8 MEMPEG-4 movie884,3 MBMPEG-4 movie476,6 MB550,8 MBMPEG-4 movie2AAGPMPEG-4 movie438,9 MBMPEG-4 movie1,68 GBMPEG-4 movie430.4 ME2,38 GBMPEG-4 movie2,26 GB386,3 MBMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GBMPEG-4 movie1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GEMPEG-4 movie627 G MPMDEG.A movid978,7 MBMPEG-4 movie798,7 MBMPEG-4 movie404.6 MPMPEG-A movie4,16 GBMPEG-4 movie319,7 MB291.7 MEMPEG-4 movie2.62 GBMoEeA movid...
|
NULL
|
-7507442273275239120
|
NULL
|
visual_change
|
ocr
|
NULL
|
FinderFavouritesE jiminny(* AirDropO Recents# Appl FinderFavouritesE jiminny(* AirDropO Recents# Applications© DocumentsG lukasIcloud• iCloud Drive992 Svnc toldeLocationsO DXP4800PLUS-B5F A® Network• CRM• Orange• Red• Yellow• Green• Blue• Purple• All lags..VIeWWinaowscreenpipearchive.db#recycledb.sqlite-shmdb.sqliteVOOSB sync.log• screenpipe.2026-05-07.0.1ogv data2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24>2026-04-222026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14• screenpipe_sync_updated.sharchive.db-oak>?appdb.salite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesShareEdit Tags~ (Q Search'Date ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterday at 14:45Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Apr 2026 at 22:2328 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Apr 2026 at 9:161/ Apr 2026 at 8.0/16 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:3126 Apr 2026 at 20:1026 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51•1 4m 55s1AA5 MR30 items, 1,94 TB available12,92 GB Document62,68 GBFolder33 KB Document2,37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder166,7 MB Folder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolder450,8 MB Folder83/.2MErolder215 GRIFolden1,09 GB20 KBTerminal scripts1113 G:193 KB FolderZero bytes15 KBTerminal scripts21 hutes ISONZero bytes13 KB Folder$088 =00FavouritesE jiminny(* AirDrop• RecentsA Applications© Documents© Downloadsii lukasiCloud• iCloud Drive283 Sync folderLocationsO DXP4800PLUS-B5F A® Network• CRM• Orange• Red• Yellow• Greer• Blue• Purple•) All lags..workv N 2026= Dally 2100:2026-05-11 at 12.1726.mр4- Daly 2993898. mрa1-12020008 107 81P4Daily 2026-05-07.mp4uw 1-12025604204.mo4* Daily 2 Rehatte- 24.mp4en User lPilcómmpredgccreanSh00 2028405-17a(12.17.26.mp4"Daily 2BUfpiRâte3.mp4Daily 2RRake An23.mp4u RefinBIRER{68,04-06.mp4- Daily 2026-04-21.mp4Da Refineroanv2026-04-20.mo4Daily 28Aêr®A-20.mp4R Daily 2026* Daly 2026-04-78. m: • • •Dlannint.2026.04.15 mn/lDaily 2Quick Aotioh$p4•Ucer- Daily 2icode Selected Video Fflesw: Daily 2New Term2 Tab HereDaily 2 New JTer7 indow HereI Daily 2028904-86.964y00xDaily 2026-04-03.mp4es Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4- Daily 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4sn Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4— Peview 2026-02-19 mn/a Planing 2026-03-18.mp4•ketro 2020-03-1/.mp4• Dailv 2026-03-17 mn/- Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.e Daily 2026-03-11.mp4• Daily 2026-03-10 mn/*: Refinement 2026-03-09.mp4• Daily 2026-03-09.mp4rn Dailv 2026-03-06.mo4sa: Plannina 2026-03-04.mD4Support Daily - in 2h 38 mQ SearchDate ModifiedToday at 12:22Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Apr 2026 at 10:2121 Apr 2026 at 11:0221 Apr 2026 at 10:0020 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Apr 2026 at 10:0015 Apr 2026 at 11:1414 Apr 2026 at 17:3714 Apr 2026 at 10:09OAnr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:016 Apr 2026 at 10:083 Apr 2026 at 10:211 Apr 2026 at 12:2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0926 Mar 2026 at 9:5924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0619 Mar 2026 at 12:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:1816 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:0610 Mar 2026 at 9:579 Mar 2026 at 17:046 Mar 2026 at 9:574 Mar 2026 at 11:091 of 154 selected, 1,94 TB availableMon 11 May 12:22:52-- Folder491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1,55 GBMPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MBMPEG-4 movie724 MB1.74 GEMPEG-4 movie1,36 GBMPEG-4 movie2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 MEMPEG-4 movie2,75 GBMPEG-4 movie1,44 CBMPEG-4 movie924,4 MBMPEG-4 movie262 6 MPMPEG-A movid748,8 MBMPEG-4 movie1,04 GB575.5 M:720,5 MBMPEG-4 movie1,02 GB4,68 GBMPEG-4 movie2AGPMDEG-A movie923,6 MBMPEG-4 movie2,77 GB641.8 MEMPEG-4 movie884,3 MBMPEG-4 movie476,6 MB550,8 MBMPEG-4 movie2AAGPMPEG-4 movie438,9 MBMPEG-4 movie1,68 GBMPEG-4 movie430.4 ME2,38 GBMPEG-4 movie2,26 GB386,3 MBMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GBMPEG-4 movie1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GEMPEG-4 movie627 G MPMDEG.A movid978,7 MBMPEG-4 movie798,7 MBMPEG-4 movie404.6 MPMPEG-A movie4,16 GBMPEG-4 movie319,7 MB291.7 MEMPEG-4 movie2.62 GBMoEeA movid...
|
16769
|
NULL
|
NULL
|
NULL
|
|
16772
|
748
|
15
|
2026-05-11T09:22:53.306895+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491373306_m1.jpg...
|
Notion Calendar
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFileEditViewGoWindowHelp<•00DEV (docker)D FinderFileEditViewGoWindowHelp<•00DEV (docker)DOCKERO ₴1DEV (docker)882APP (-zsh)|• жзmasterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]• Support Daily • in 2h 38 m-zsh84-zsh885100% <78• Mon 11 May 12:22:53181screenpipe"#6DEV...
|
NULL
|
-4496222486862267805
|
NULL
|
click
|
ocr
|
NULL
|
FinderFileEditViewGoWindowHelp<•00DEV (docker)D FinderFileEditViewGoWindowHelp<•00DEV (docker)DOCKERO ₴1DEV (docker)882APP (-zsh)|• жзmasterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]• Support Daily • in 2h 38 m-zsh84-zsh885100% <78• Mon 11 May 12:22:53181screenpipe"#6DEV...
|
16770
|
NULL
|
NULL
|
NULL
|
|
16773
|
749
|
16
|
2026-05-11T09:22:55.646097+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491375646_m2.jpg...
|
Notion Calendar
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderconVIeWFavouritesjiminny() AirDrop@ RecentsA FinderconVIeWFavouritesjiminny() AirDrop@ RecentsA Applications|9 Documents• Downloadsii lukasIcloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 NetworkAAр!.• Orange• Red• Yellow• Greer• Blue• Purple• All lags..Windowmelpscreenpipearchive.db› _ #recycledb.sqlite-shmdb.sqlitevi loassync.log• screenpipe.2026-05-07.0.1ogv data•2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24• 2026-04-22•2026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14- screenpipe_sync_updated.sharchive.db-oak>?app• db.sqlite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesEdit Tagsv Q SearchDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterdav at 14:45Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Anr 2026 at 22:22128 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Anr 2026 nt 0:1617 Apr 2026 at 8:5716 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:312A Anr 2026 20:10.26 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51•1 4m 55s1AA5 MR30 items, 1.94 TB availabld12,92 GB Document62,68 GBFolder33 KB Document2.37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder1667 MP Calder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolderA50 9 MP Soldon83/.2ME215 GRIrolderFolden1,09 GB20 KBTerminal scripts11.13 GB193 KB FolderZero bytesDocument15 KBTerminal scripts21 butes ISONIZero bytes13 KB Folder•• cFavourites• jiminny(®) AirDrop• Recents* Applications|9 Documents• Downloadsii lukasiCloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 Networ!• CRMI• Orange• Red• Yellow• Greero Bue• Purple•) All lags..workv 2026= CleanShot 2026-05-11 at 12.17.26.mp4= Dally 2026-05-11.mp4a Dailv 2026-05-08 mo41-1 2026-05-07.mp4i Daily 2026-05-07.mp4шгN 1-1 2026-04-24,m04• Daily 2026-04-24.mp4ms User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4** Refinement 2026-04-06.mp4Dally 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4R Daily 2026-04-17.mp4ru Daily 2026-04-16.mp4# Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4- Daily 2026-04-09.mp4wa Daily 2026-04-08.mp4• Dailv 2026-04-07 mo4Daily 2026-04-06.mp4las Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4• Dailv 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4am Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/a Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/_: Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.2s. Daily 2026-03-11.mp4• Daily 2026-03-10 mn/xe: Refinement 2026-03-09.mp4Ra Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4H Dlannina ?096 Ло Ли mnл: Support Daily - in 2h 38 m100% 2• Mon 11 May 12:22:55vQ Sear!Date ModifiedTodau at 12.29Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Anr 2026 at 10:2121 Apr 2026 at 11:0220 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Anr 2026 at 11:1414 Apr 2026 at 10:09OAnr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:01A Anr 2026 at 10:091 Apr 2026 at 12:2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0610 Mar 206 ct 19:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:06aMer nand nt 47:046 Mar 2026 at 9:57—Caldon491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1.55 GBMPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie126 GRIMDEG-A movid2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 ME2,75 GB1,44 GB924,4 MB262 6 MP748.8 MBMPEG-4 movieMDEG.A movidMPEG-4 movieMPEG-4 movieMPEG-A movidMPEG-4 movie1,04 GB575.5 M:720 6 MRMDEG.A movid1,02 GB4,68 GBMPEG-4 movie2AGPMDEG-A movie923,6 MBMPEG-4 movie2,77 GB641.8 ME99/2MRMPEG-4 movieMDEC.A movid550.8 M:2AAGP438.9 MB1,68 GBMPEG-4 movieMDEG-A movidMPEG-4 movieMPEG-4 movie430.4 ME2.38 GBMDEC.A movid2,26 GB386.3 MEMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GB1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GE627 G MP978,7 MB798,7 MEMPEG-4 movieMDEG.A movidMDEG-A movieMPEG-4 movieMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movienen cplMDEeA movitAoe ACAcalaatad 4 0l TO aucilahl)...
|
NULL
|
4815810015255146051
|
NULL
|
visual_change
|
ocr
|
NULL
|
FinderconVIeWFavouritesjiminny() AirDrop@ RecentsA FinderconVIeWFavouritesjiminny() AirDrop@ RecentsA Applications|9 Documents• Downloadsii lukasIcloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 NetworkAAр!.• Orange• Red• Yellow• Greer• Blue• Purple• All lags..Windowmelpscreenpipearchive.db› _ #recycledb.sqlite-shmdb.sqlitevi loassync.log• screenpipe.2026-05-07.0.1ogv data•2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24• 2026-04-22•2026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14- screenpipe_sync_updated.sharchive.db-oak>?app• db.sqlite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesEdit Tagsv Q SearchDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterdav at 14:45Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Anr 2026 at 22:22128 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Anr 2026 nt 0:1617 Apr 2026 at 8:5716 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:312A Anr 2026 20:10.26 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51•1 4m 55s1AA5 MR30 items, 1.94 TB availabld12,92 GB Document62,68 GBFolder33 KB Document2.37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder1667 MP Calder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolderA50 9 MP Soldon83/.2ME215 GRIrolderFolden1,09 GB20 KBTerminal scripts11.13 GB193 KB FolderZero bytesDocument15 KBTerminal scripts21 butes ISONIZero bytes13 KB Folder•• cFavourites• jiminny(®) AirDrop• Recents* Applications|9 Documents• Downloadsii lukasiCloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 Networ!• CRMI• Orange• Red• Yellow• Greero Bue• Purple•) All lags..workv 2026= CleanShot 2026-05-11 at 12.17.26.mp4= Dally 2026-05-11.mp4a Dailv 2026-05-08 mo41-1 2026-05-07.mp4i Daily 2026-05-07.mp4шгN 1-1 2026-04-24,m04• Daily 2026-04-24.mp4ms User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4** Refinement 2026-04-06.mp4Dally 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4R Daily 2026-04-17.mp4ru Daily 2026-04-16.mp4# Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4- Daily 2026-04-09.mp4wa Daily 2026-04-08.mp4• Dailv 2026-04-07 mo4Daily 2026-04-06.mp4las Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4• Dailv 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4am Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/a Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/_: Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.2s. Daily 2026-03-11.mp4• Daily 2026-03-10 mn/xe: Refinement 2026-03-09.mp4Ra Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4H Dlannina ?096 Ло Ли mnл: Support Daily - in 2h 38 m100% 2• Mon 11 May 12:22:55vQ Sear!Date ModifiedTodau at 12.29Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Anr 2026 at 10:2121 Apr 2026 at 11:0220 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Anr 2026 at 11:1414 Apr 2026 at 10:09OAnr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:01A Anr 2026 at 10:091 Apr 2026 at 12:2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0610 Mar 206 ct 19:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:06aMer nand nt 47:046 Mar 2026 at 9:57—Caldon491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1.55 GBMPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie126 GRIMDEG-A movid2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 ME2,75 GB1,44 GB924,4 MB262 6 MP748.8 MBMPEG-4 movieMDEG.A movidMPEG-4 movieMPEG-4 movieMPEG-A movidMPEG-4 movie1,04 GB575.5 M:720 6 MRMDEG.A movid1,02 GB4,68 GBMPEG-4 movie2AGPMDEG-A movie923,6 MBMPEG-4 movie2,77 GB641.8 ME99/2MRMPEG-4 movieMDEC.A movid550.8 M:2AAGP438.9 MB1,68 GBMPEG-4 movieMDEG-A movidMPEG-4 movieMPEG-4 movie430.4 ME2.38 GBMDEC.A movid2,26 GB386.3 MEMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GB1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GE627 G MP978,7 MB798,7 MEMPEG-4 movieMDEG.A movidMDEG-A movieMPEG-4 movieMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movienen cplMDEeA movitAoe ACAcalaatad 4 0l TO aucilahl)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16774
|
748
|
16
|
2026-05-11T09:23:15.848492+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491395848_m1.jpg...
|
Finder
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFileEditViewGoWindowHelpDEV (docker)• жз> FinderFileEditViewGoWindowHelpDEV (docker)• жз>0.-zshlabol• Support Daily • in 2h 37 mDOCKER-₴81DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]84-zsh885100% <78• Mon 11 May 12:23:15181screenpipe"0 ₴6DEV...
|
NULL
|
2964950506319587440
|
NULL
|
click
|
ocr
|
NULL
|
FinderFileEditViewGoWindowHelpDEV (docker)• жз> FinderFileEditViewGoWindowHelpDEV (docker)• жз>0.-zshlabol• Support Daily • in 2h 37 mDOCKER-₴81DEV (docker)882APP (-zsh)|masterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]84-zsh885100% <78• Mon 11 May 12:23:15181screenpipe"0 ₴6DEV...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16775
|
749
|
17
|
2026-05-11T09:23:15.833053+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491395833_m2.jpg...
|
Finder
|
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFavouritesjiminny() AirDrop• RecentsA Applic FinderFavouritesjiminny() AirDrop• RecentsA Applications|9 Documents• Downloadsii lukasIcloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 NetworkAAр!.• Orange• Red• Yellow• Greer• Blue• Purple• All lags..conVIeWWindowmelpscreenpipearchive.db› _ #recycledb.sqlite-shmdb.sqlitevi loassync.log• screenpipe.2026-05-07.0.1ogv data•2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24• 2026-04-22•2026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14- screenpipe_sync_updated.sharchive.db-oak>?app• db.sqlite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesEdit Tagsv Q SearchDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterdav at 14:45Yesterday at 13:47Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Aar 2026 at 22:2228 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Anr 2026 nt 0:1617 Apr 2026 at 8:5716 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:312A Anr 2026 20:10.26 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51v Size12,92 GB Document62,68 GBFolder33 KB Document2.37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder1667 MP Calder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolderA50 9 MP Soldon83/.2ME215 GRIrolderFolden1,09 GB20 KBTerminal scripts11.13 GB193 KB FolderZero bytesDocument15 KBTerminal scripts21 butes ISONIZero bytes13 KB Folder•1 4m 55s1AA5 MR30 items, 1.94 TB availabldhhd•• cFavourites• jiminny(®) AirDrop• Recents* Applications|9 Documents• Downloadsii lukasiCloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 Networ!• CRMI• Orange• Red• Yellow• Greero Bue• Purple•) All lags..workv 2026= PLanhat Petko interest event 2026-05-11.mp4= Dally 2026-05-11.mp4ни Daily 2026-05-08.mp4* 1-1 2026-05-07.mp4i Daily 2026-05-07.mp4шгя 1-1 2026-04-24,mo4• Daily 2026-04-24.mp4ms User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4** Refinement 2026-04-06.mp4Dally 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4R Daily 2026-04-17.mp4ru Daily 2026-04-16.mp4# Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4- Daily 2026-04-09.mp4wa Daily 2026-04-08.mp4• Dailv 2026-04-07 mo4Daily 2026-04-06.mp4las Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4• Dailv 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4am Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/a Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/_: Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.2s. Daily 2026-03-11.mp4• Daily 2026-03-10 mn/xe: Refinement 2026-03-09.mp4Ra Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4H Dlannina ?096 Ло Ли mnл: Support Daily - in 2 h 37 mvQ Sear!Date ModifiedToday at 12:23Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Anr 2026 at 10:2121 Apr 2026 at 11:0220 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Anr 2026 at 11:1414 Apr 2026 at 10:09OAnr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:13A Anr 2026 at 10:091 Apr 2026 at 12:2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0610 Mar 206 ct 19:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:06aMer nand nt 47:046 Mar 2026 at 9:57100% 2• Mon 11 May 12:23:15491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1.55 GB MPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie126 GRIMDEG-A movid2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 MEMPEG-4 movie2,75 GBMDEG.A movid1,44 GBMPEG-4 movie924,4 MBMPEG-4 movie262 6 MPMPEG-A movid748.8 MBMPEG-4 movie1,04 GB575.5 M:720 6 MRMDEG.A movid1,02 GB4,68 GBMPEG-4 movie2AGPMDEG-A movie923,6 MBMPEG-4 movie2,77 GB641.8 ME99/2MRMPEG-4 movieMDEC.A movid550.8 M:2AAGP438.9 MB1,68 GBMPEG-4 movieMDEG-A movidMPEG-4 movieMPEG-4 movie430.4 ME2.38 GBMDEC.A movid2,26 GB386.3 MEMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GB1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GE627 G MP978,7 MB798,7 MEMPEG-4 movieMDEG.A movidMDEG-A movieMPEG-4 movieMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movienen cplMDEeA movitAoe ACAcalaatad 4 0l TO aucilahl)...
|
NULL
|
-9015977338143473138
|
NULL
|
click
|
ocr
|
NULL
|
FinderFavouritesjiminny() AirDrop• RecentsA Applic FinderFavouritesjiminny() AirDrop• RecentsA Applications|9 Documents• Downloadsii lukasIcloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 NetworkAAр!.• Orange• Red• Yellow• Greer• Blue• Purple• All lags..conVIeWWindowmelpscreenpipearchive.db› _ #recycledb.sqlite-shmdb.sqlitevi loassync.log• screenpipe.2026-05-07.0.1ogv data•2026-05-07• 2026-05-062026-04.292026-04-27> 2026-04-25•2026-04-24• 2026-04-22•2026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-152026-04-14- screenpipe_sync_updated.sharchive.db-oak>?app• db.sqlite-walscreenpipe_sync.shann cettinas ison• screenpipe.db›_pipesEdit Tagsv Q SearchDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterdav at 14:45Yesterday at 13:47Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Aar 2026 at 22:2228 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Anr 2026 nt 0:1617 Apr 2026 at 8:5716 Anr 2026 at 0:1215 Apr 2026 at 9:59Yesterday at 13:06Yesterdav at 12:312A Anr 2026 20:10.26 Apr 2026 at 17:1718 Apr 2026 at 18.3518 Anr 2026 at 17:1213 Apr 2026 at 17:2111 Apr 2026 at 16:51v Size12,92 GB Document62,68 GBFolder33 KB Document2.37 GB Document573 KB Folder7 KB Log File566 KB Loa File7,2 CB Folder305,6 MBFolder18,8 MBFolder1667 MP Calder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB Folder171,8 MBroldel525.4 MBFolderA50 9 MP Soldon83/.2ME215 GRIrolderFolden1,09 GB20 KBTerminal scripts11.13 GB193 KB FolderZero bytesDocument15 KBTerminal scripts21 butes ISONIZero bytes13 KB Folder•1 4m 55s1AA5 MR30 items, 1.94 TB availabldhhd•• cFavourites• jiminny(®) AirDrop• Recents* Applications|9 Documents• Downloadsii lukasiCloud• iCloud Drive992 Svnc toldeLocations0 DXP4800PLUS-B5F A49 Networ!• CRMI• Orange• Red• Yellow• Greero Bue• Purple•) All lags..workv 2026= PLanhat Petko interest event 2026-05-11.mp4= Dally 2026-05-11.mp4ни Daily 2026-05-08.mp4* 1-1 2026-05-07.mp4i Daily 2026-05-07.mp4шгя 1-1 2026-04-24,mo4• Daily 2026-04-24.mp4ms User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4** Refinement 2026-04-06.mp4Dally 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4R Daily 2026-04-17.mp4ru Daily 2026-04-16.mp4# Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4- Daily 2026-04-09.mp4wa Daily 2026-04-08.mp4• Dailv 2026-04-07 mo4Daily 2026-04-06.mp4las Planning 2026-04-01 & task split.mp41: Retra 2026-02-21 mn4- Refinement 2026-03-30.mp4a Dailv 2026-03-30.mo4- Daily 2026-03-27.mp4- Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4• Dailv 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4am Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/a Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/_: Refinement 2026-03-16.mp4- Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 11 2026.02-12 mл/.2s. Daily 2026-03-11.mp4• Daily 2026-03-10 mn/xe: Refinement 2026-03-09.mp4Ra Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4H Dlannina ?096 Ло Ли mnл: Support Daily - in 2 h 37 mvQ Sear!Date ModifiedToday at 12:23Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Anr 2026 at 10:2121 Apr 2026 at 11:0220 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Anr 2026 at 11:1414 Apr 2026 at 10:09OAnr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:13A Anr 2026 at 10:091 Apr 2026 at 12:2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0610 Mar 206 ct 19:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2018 Mar 2026 at 11:1417 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:06aMer nand nt 47:046 Mar 2026 at 9:57100% 2• Mon 11 May 12:23:15491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1.55 GB MPEG-4 movie931,7 MBMPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie126 GRIMDEG-A movid2,41 GBMPEG-4 movie567,8 MBMPEG-4 movie4.25 G:MPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 MEMPEG-4 movie2,75 GBMDEG.A movid1,44 GBMPEG-4 movie924,4 MBMPEG-4 movie262 6 MPMPEG-A movid748.8 MBMPEG-4 movie1,04 GB575.5 M:720 6 MRMDEG.A movid1,02 GB4,68 GBMPEG-4 movie2AGPMDEG-A movie923,6 MBMPEG-4 movie2,77 GB641.8 ME99/2MRMPEG-4 movieMDEC.A movid550.8 M:2AAGP438.9 MB1,68 GBMPEG-4 movieMDEG-A movidMPEG-4 movieMPEG-4 movie430.4 ME2.38 GBMDEC.A movid2,26 GB386.3 MEMPEG-4 movie705 9 MPMDSG-A movie2,78 GBMPEG-4 movie1,53 GB1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GE627 G MP978,7 MB798,7 MEMPEG-4 movieMDEG.A movidMDEG-A movieMPEG-4 movieMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movienen cplMDEeA movitAoe ACAcalaatad 4 0l TO aucilahl)...
|
16773
|
NULL
|
NULL
|
NULL
|
|
16776
|
748
|
17
|
2026-05-11T09:23:17.010121+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491397010_m1.jpg...
|
Finder
|
screenpipe
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Favourites
jiminny
AirDrop
Recents
Applications
Do Favourites
jiminny
AirDrop
Recents
Applications
Documents
Downloads
lukas
iCloud
iCloud Drive
Sync folder
Locations
DXP4800PLUS-B5F
Eject
Network
Tags
CRM
Orange
Red
Yellow
Green
Blue
Purple
All Tags…
Name
Date Modified
Size
Kind
archive.db
Yesterday at 20:48
12,92 GB
Document
#recycle
Yesterday at 20:47
62,68 GB
Folder
db.sqlite-shm
Yesterday at 14:49
33 KB
Document
db.sqlite
Yesterday at 14:45
2,37 GB
Document
logs
Yesterday at 13:47
573 KB
Folder
sync.log
Yesterday at 13:47
7 KB
Log File
screenpipe.2026-05-07.0.log
7 May 2026 at 21:50
566 KB
Log File
data
Yesterday at 13:46
7,2 GB
Folder
2026-05-07
8 May 2026 at 9:26
305,6 MB
Folder
2026-05-06
6 May 2026 at 21:02
18,8 MB
Folder
2026-04-28
28 Apr 2026 at 22:23
166,7 MB
Folder
2026-04-27
28 Apr 2026 at 9:19
339,8 MB
Folder
2026-04-25
26 Apr 2026 at 16:35
39,7 MB
Folder
2026-04-24
24 Apr 2026 at 22:30
149,1 MB
Folder
2026-04-22
24 Apr 2026 at 12:08
265,5 MB
Folder
2026-04-23
24 Apr 2026 at 12:07
171,8 MB
Folder
2026-04-20
22 Apr 2026 at 18:44
525,4 MB
Folder
2026-04-21
22 Apr 2026 at 9:16
450,8 MB
Folder
2026-04-17
18 Apr 2026 at 13:35
699,6 MB
Folder
2026-04-16
17 Apr 2026 at 8:57
837,2 MB
Folder
2026-04-15
16 Apr 2026 at 9:13
2,15 GB
Folder
2026-04-14
15 Apr 2026 at 9:59
1,09 GB
Folder
screenpipe_sync_updated.sh
Yesterday at 13:06
20 KB
Terminal scripts
archive.db-bak
Yesterday at 12:31
11,13 GB
Document
app
26 Apr 2026 at 20:10
193 KB
Folder
db.sqlite-wal
26 Apr 2026 at 17:17
Zero bytes
Document
screenpipe_sync.sh
18 Apr 2026 at 18:35
15 KB
Terminal scripts
app_settings.json
18 Apr 2026 at 17:42
31 bytes
JSON
screenpipe.db
13 Apr 2026 at 17:21
Zero bytes
Document
pipes
11 Apr 2026 at 16:51
13 KB
Folder
Name
Date Modified
Size
Kind
30 items, 1,94 TB available
screenpipe...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Favourites","depth":6,"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"jiminny","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"AirDrop","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Recents","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Applications","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Documents","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Downloads","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"lukas","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"iCloud","depth":6,"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"iCloud Drive","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sync folder","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Locations","depth":6,"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"DXP4800PLUS-B5F","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Eject","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"Network","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Tags","depth":6,"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"CRM","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Orange","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Red","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yellow","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Green","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Blue","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Purple","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"All Tags…","depth":6,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Name","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Date Modified","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Size","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Kind","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"archive.db","depth":7,"on_screen":true,"value":"archive.db","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 20:48","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"12,92 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"#recycle","depth":7,"on_screen":true,"value":"#recycle","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 20:47","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"62,68 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"db.sqlite-shm","depth":7,"on_screen":true,"value":"db.sqlite-shm","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 14:49","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"33 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"db.sqlite","depth":7,"on_screen":true,"value":"db.sqlite","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 14:45","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2,37 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"logs","depth":7,"on_screen":true,"value":"logs","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:47","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"573 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"sync.log","depth":7,"on_screen":true,"value":"sync.log","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:47","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"7 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Log File","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe.2026-05-07.0.log","depth":7,"on_screen":true,"value":"screenpipe.2026-05-07.0.log","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"7 May 2026 at 21:50","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"566 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Log File","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"data","depth":7,"on_screen":true,"value":"data","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:46","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"7,2 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-05-07","depth":7,"on_screen":true,"value":"2026-05-07","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"8 May 2026 at 9:26","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"305,6 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-05-06","depth":7,"on_screen":true,"value":"2026-05-06","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"6 May 2026 at 21:02","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18,8 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-28","depth":7,"on_screen":true,"value":"2026-04-28","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"28 Apr 2026 at 22:23","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"166,7 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-27","depth":7,"on_screen":true,"value":"2026-04-27","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"28 Apr 2026 at 9:19","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"339,8 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-25","depth":7,"on_screen":true,"value":"2026-04-25","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"26 Apr 2026 at 16:35","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"39,7 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-24","depth":7,"on_screen":true,"value":"2026-04-24","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"24 Apr 2026 at 22:30","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"149,1 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-22","depth":7,"on_screen":true,"value":"2026-04-22","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"24 Apr 2026 at 12:08","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"265,5 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-23","depth":7,"on_screen":true,"value":"2026-04-23","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"24 Apr 2026 at 12:07","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"171,8 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-20","depth":7,"on_screen":true,"value":"2026-04-20","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"22 Apr 2026 at 18:44","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"525,4 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-21","depth":7,"on_screen":true,"value":"2026-04-21","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"22 Apr 2026 at 9:16","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"450,8 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-17","depth":7,"on_screen":true,"value":"2026-04-17","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"18 Apr 2026 at 13:35","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"699,6 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-16","depth":7,"on_screen":true,"value":"2026-04-16","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"17 Apr 2026 at 8:57","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"837,2 MB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-15","depth":7,"on_screen":true,"value":"2026-04-15","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"16 Apr 2026 at 9:13","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2,15 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-14","depth":7,"on_screen":true,"value":"2026-04-14","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"15 Apr 2026 at 9:59","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1,09 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe_sync_updated.sh","depth":7,"on_screen":true,"value":"screenpipe_sync_updated.sh","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:06","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"20 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Terminal scripts","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"archive.db-bak","depth":7,"on_screen":true,"value":"archive.db-bak","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 12:31","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"11,13 GB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"app","depth":7,"on_screen":true,"value":"app","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"26 Apr 2026 at 20:10","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"193 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"db.sqlite-wal","depth":7,"on_screen":true,"value":"db.sqlite-wal","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"26 Apr 2026 at 17:17","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Zero bytes","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe_sync.sh","depth":7,"on_screen":true,"value":"screenpipe_sync.sh","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"18 Apr 2026 at 18:35","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"15 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Terminal scripts","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"app_settings.json","depth":7,"on_screen":true,"value":"app_settings.json","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"18 Apr 2026 at 17:42","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"31 bytes","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JSON","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe.db","depth":7,"on_screen":true,"value":"screenpipe.db","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"13 Apr 2026 at 17:21","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Zero bytes","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"pipes","depth":7,"on_screen":true,"value":"pipes","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"11 Apr 2026 at 16:51","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"13 KB","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Name","depth":6,"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Date Modified","depth":6,"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Size","depth":6,"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Kind","depth":6,"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"30 items, 1,94 TB available","depth":2,"bounds":{"left":0.0,"top":0.0,"width":0.10763889,"height":0.015555556},"on_screen":true,"automation_id":"_NS:34","role_description":"text"},{"role":"AXStaticText","text":"screenpipe","depth":1,"on_screen":true,"role_description":"text"}]...
|
6392771323366051413
|
-2647386114622522493
|
click
|
accessibility
|
NULL
|
Favourites
jiminny
AirDrop
Recents
Applications
Do Favourites
jiminny
AirDrop
Recents
Applications
Documents
Downloads
lukas
iCloud
iCloud Drive
Sync folder
Locations
DXP4800PLUS-B5F
Eject
Network
Tags
CRM
Orange
Red
Yellow
Green
Blue
Purple
All Tags…
Name
Date Modified
Size
Kind
archive.db
Yesterday at 20:48
12,92 GB
Document
#recycle
Yesterday at 20:47
62,68 GB
Folder
db.sqlite-shm
Yesterday at 14:49
33 KB
Document
db.sqlite
Yesterday at 14:45
2,37 GB
Document
logs
Yesterday at 13:47
573 KB
Folder
sync.log
Yesterday at 13:47
7 KB
Log File
screenpipe.2026-05-07.0.log
7 May 2026 at 21:50
566 KB
Log File
data
Yesterday at 13:46
7,2 GB
Folder
2026-05-07
8 May 2026 at 9:26
305,6 MB
Folder
2026-05-06
6 May 2026 at 21:02
18,8 MB
Folder
2026-04-28
28 Apr 2026 at 22:23
166,7 MB
Folder
2026-04-27
28 Apr 2026 at 9:19
339,8 MB
Folder
2026-04-25
26 Apr 2026 at 16:35
39,7 MB
Folder
2026-04-24
24 Apr 2026 at 22:30
149,1 MB
Folder
2026-04-22
24 Apr 2026 at 12:08
265,5 MB
Folder
2026-04-23
24 Apr 2026 at 12:07
171,8 MB
Folder
2026-04-20
22 Apr 2026 at 18:44
525,4 MB
Folder
2026-04-21
22 Apr 2026 at 9:16
450,8 MB
Folder
2026-04-17
18 Apr 2026 at 13:35
699,6 MB
Folder
2026-04-16
17 Apr 2026 at 8:57
837,2 MB
Folder
2026-04-15
16 Apr 2026 at 9:13
2,15 GB
Folder
2026-04-14
15 Apr 2026 at 9:59
1,09 GB
Folder
screenpipe_sync_updated.sh
Yesterday at 13:06
20 KB
Terminal scripts
archive.db-bak
Yesterday at 12:31
11,13 GB
Document
app
26 Apr 2026 at 20:10
193 KB
Folder
db.sqlite-wal
26 Apr 2026 at 17:17
Zero bytes
Document
screenpipe_sync.sh
18 Apr 2026 at 18:35
15 KB
Terminal scripts
app_settings.json
18 Apr 2026 at 17:42
31 bytes
JSON
screenpipe.db
13 Apr 2026 at 17:21
Zero bytes
Document
pipes
11 Apr 2026 at 16:51
13 KB
Folder
Name
Date Modified
Size
Kind
30 items, 1,94 TB available
screenpipe...
|
16774
|
NULL
|
NULL
|
NULL
|
|
16777
|
749
|
18
|
2026-05-11T09:23:17.119496+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491397119_m2.jpg...
|
Finder
|
screenpipe
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Favourites
jiminny
AirDrop
Recents
Applications
Do Favourites
jiminny
AirDrop
Recents
Applications
Documents
Downloads
lukas
iCloud
iCloud Drive
Sync folder
Locations
DXP4800PLUS-B5F
Eject
Network
Tags
CRM
Orange
Red
Yellow
Green
Blue
Purple
All Tags…
Name
Date Modified
Size
Kind
archive.db
Yesterday at 20:48
12,92 GB
Document
#recycle
Yesterday at 20:47
62,68 GB
Folder
db.sqlite-shm
Yesterday at 14:49
33 KB
Document
db.sqlite
Yesterday at 14:45
2,37 GB
Document
logs
Yesterday at 13:47
573 KB
Folder
sync.log
Yesterday at 13:47
7 KB
Log File
screenpipe.2026-05-07.0.log
7 May 2026 at 21:50
566 KB
Log File
data
Yesterday at 13:46
7,2 GB
Folder
2026-05-07
8 May 2026 at 9:26
305,6 MB
Folder
2026-05-06
6 May 2026 at 21:02
18,8 MB
Folder
2026-04-28
28 Apr 2026 at 22:23
166,7 MB
Folder
2026-04-27
28 Apr 2026 at 9:19
339,8 MB
Folder
2026-04-25
26 Apr 2026 at 16:35
39,7 MB
Folder
2026-04-24
24 Apr 2026 at 22:30
149,1 MB
Folder
2026-04-22
24 Apr 2026 at 12:08
265,5 MB
Folder
2026-04-23
24 Apr 2026 at 12:07
171,8 MB
Folder
2026-04-20
22 Apr 2026 at 18:44
525,4 MB
Folder
2026-04-21
22 Apr 2026 at 9:16
450,8 MB
Folder
2026-04-17
18 Apr 2026 at 13:35
699,6 MB
Folder
2026-04-16
17 Apr 2026 at 8:57
837,2 MB
Folder
2026-04-15
16 Apr 2026 at 9:13
2,15 GB
Folder
2026-04-14
15 Apr 2026 at 9:59
1,09 GB
Folder
screenpipe_sync_updated.sh
Yesterday at 13:06
20 KB
Terminal scripts
archive.db-bak
Yesterday at 12:31
11,13 GB
Document
app
26 Apr 2026 at 20:10
193 KB
Folder
db.sqlite-wal
26 Apr 2026 at 17:17
Zero bytes
Document
screenpipe_sync.sh
18 Apr 2026 at 18:35
15 KB
Terminal scripts
app_settings.json
18 Apr 2026 at 17:42
31 bytes
JSON
screenpipe.db
13 Apr 2026 at 17:21
Zero bytes
Document
pipes
11 Apr 2026 at 16:51
13 KB
Folder
Name
Date Modified
Size
Kind
30 items, 1,94 TB available
screenpipe...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Favourites","depth":6,"bounds":{"left":0.004654255,"top":0.061452515,"width":0.06216755,"height":0.015163607},"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"jiminny","depth":6,"bounds":{"left":0.012632979,"top":0.08140463,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"AirDrop","depth":6,"bounds":{"left":0.012632979,"top":0.103751,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Recents","depth":6,"bounds":{"left":0.012632979,"top":0.12609737,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Applications","depth":6,"bounds":{"left":0.012632979,"top":0.14844373,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Documents","depth":6,"bounds":{"left":0.012632979,"top":0.1707901,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Downloads","depth":6,"bounds":{"left":0.012632979,"top":0.19313647,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"lukas","depth":6,"bounds":{"left":0.012632979,"top":0.21548285,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"iCloud","depth":6,"bounds":{"left":0.004654255,"top":0.2434158,"width":0.06216755,"height":0.015163607},"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"iCloud Drive","depth":6,"bounds":{"left":0.012632979,"top":0.26336792,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sync folder","depth":6,"bounds":{"left":0.012632979,"top":0.2857143,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Locations","depth":6,"bounds":{"left":0.004654255,"top":0.31364724,"width":0.06216755,"height":0.015163607},"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"DXP4800PLUS-B5F","depth":6,"bounds":{"left":0.012632979,"top":0.33359936,"width":0.043218084,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Eject","depth":6,"bounds":{"left":0.05651596,"top":0.33519554,"width":0.0043218085,"height":0.009577015},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"Network","depth":6,"bounds":{"left":0.012632979,"top":0.35594574,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Tags","depth":6,"bounds":{"left":0.004654255,"top":0.38387868,"width":0.06216755,"height":0.015163607},"on_screen":true,"automation_id":"xSidebarHeader","role_description":"text"},{"role":"AXStaticText","text":"CRM","depth":6,"bounds":{"left":0.012632979,"top":0.4038308,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Orange","depth":6,"bounds":{"left":0.012632979,"top":0.42617717,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Red","depth":6,"bounds":{"left":0.012632979,"top":0.44852355,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Yellow","depth":6,"bounds":{"left":0.012632979,"top":0.4708699,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Green","depth":6,"bounds":{"left":0.012632979,"top":0.49321628,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Blue","depth":6,"bounds":{"left":0.012632979,"top":0.51556265,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Purple","depth":6,"bounds":{"left":0.012632979,"top":0.53790903,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"All Tags…","depth":6,"bounds":{"left":0.012632979,"top":0.5602554,"width":0.049534574,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Name","depth":7,"bounds":{"left":0.08277926,"top":0.06624102,"width":0.011635638,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Date Modified","depth":7,"bounds":{"left":0.36569148,"top":0.06624102,"width":0.026928192,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Size","depth":7,"bounds":{"left":0.42586437,"top":0.06624102,"width":0.008976064,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Kind","depth":7,"bounds":{"left":0.4581117,"top":0.06624102,"width":0.00930851,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"archive.db","depth":7,"bounds":{"left":0.08277926,"top":0.08938547,"width":0.023936171,"height":0.012769354},"on_screen":true,"value":"archive.db","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 20:48","depth":7,"bounds":{"left":0.36569148,"top":0.08938547,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"12,92 GB","depth":7,"bounds":{"left":0.43417552,"top":0.08938547,"width":0.020611702,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"bounds":{"left":0.4581117,"top":0.08938547,"width":0.023603724,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"#recycle","depth":7,"bounds":{"left":0.08277926,"top":0.105347164,"width":0.019946808,"height":0.012769354},"on_screen":true,"value":"#recycle","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 20:47","depth":7,"bounds":{"left":0.36569148,"top":0.105347164,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"62,68 GB","depth":7,"bounds":{"left":0.43417552,"top":0.105347164,"width":0.020611702,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.105347164,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"db.sqlite-shm","depth":7,"bounds":{"left":0.08277926,"top":0.121308856,"width":0.030585106,"height":0.012769354},"on_screen":true,"value":"db.sqlite-shm","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 14:49","depth":7,"bounds":{"left":0.36569148,"top":0.121308856,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"33 KB","depth":7,"bounds":{"left":0.44115692,"top":0.121308856,"width":0.013630319,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"bounds":{"left":0.4581117,"top":0.121308856,"width":0.023603724,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"db.sqlite","depth":7,"bounds":{"left":0.08277926,"top":0.13727055,"width":0.020279255,"height":0.012769354},"on_screen":true,"value":"db.sqlite","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 14:45","depth":7,"bounds":{"left":0.36569148,"top":0.13727055,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2,37 GB","depth":7,"bounds":{"left":0.4368351,"top":0.13727055,"width":0.017952127,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"bounds":{"left":0.4581117,"top":0.13727055,"width":0.023603724,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"logs","depth":7,"bounds":{"left":0.08277926,"top":0.15323225,"width":0.011303191,"height":0.012769354},"on_screen":true,"value":"logs","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:47","depth":7,"bounds":{"left":0.36569148,"top":0.15323225,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"573 KB","depth":7,"bounds":{"left":0.43849733,"top":0.15323225,"width":0.016289894,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.15323225,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"sync.log","depth":7,"bounds":{"left":0.087765954,"top":0.16919394,"width":0.019614361,"height":0.012769354},"on_screen":true,"value":"sync.log","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:47","depth":7,"bounds":{"left":0.36569148,"top":0.16919394,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"7 KB","depth":7,"bounds":{"left":0.44381648,"top":0.16919394,"width":0.010970744,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Log File","depth":7,"bounds":{"left":0.4581117,"top":0.16919394,"width":0.017287234,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe.2026-05-07.0.log","depth":7,"bounds":{"left":0.087765954,"top":0.18515563,"width":0.061835106,"height":0.012769354},"on_screen":true,"value":"screenpipe.2026-05-07.0.log","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"7 May 2026 at 21:50","depth":7,"bounds":{"left":0.36569148,"top":0.18515563,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"566 KB","depth":7,"bounds":{"left":0.43849733,"top":0.18515563,"width":0.016289894,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Log File","depth":7,"bounds":{"left":0.4581117,"top":0.18515563,"width":0.017287234,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"data","depth":7,"bounds":{"left":0.08277926,"top":0.20111732,"width":0.011635638,"height":0.012769354},"on_screen":true,"value":"data","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:46","depth":7,"bounds":{"left":0.36569148,"top":0.20111732,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"7,2 GB","depth":7,"bounds":{"left":0.43949467,"top":0.20111732,"width":0.015292553,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.20111732,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-05-07","depth":7,"bounds":{"left":0.087765954,"top":0.21707901,"width":0.027925532,"height":0.012769354},"on_screen":true,"value":"2026-05-07","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"8 May 2026 at 9:26","depth":7,"bounds":{"left":0.36569148,"top":0.21707901,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"305,6 MB","depth":7,"bounds":{"left":0.43351063,"top":0.21707901,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.21707901,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-05-06","depth":7,"bounds":{"left":0.087765954,"top":0.2330407,"width":0.02825798,"height":0.012769354},"on_screen":true,"value":"2026-05-06","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"6 May 2026 at 21:02","depth":7,"bounds":{"left":0.36569148,"top":0.2330407,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18,8 MB","depth":7,"bounds":{"left":0.43650267,"top":0.2330407,"width":0.01861702,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.2330407,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-28","depth":7,"bounds":{"left":0.087765954,"top":0.2490024,"width":0.02825798,"height":0.012769354},"on_screen":true,"value":"2026-04-28","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"28 Apr 2026 at 22:23","depth":7,"bounds":{"left":0.36569148,"top":0.2490024,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"166,7 MB","depth":7,"bounds":{"left":0.43351063,"top":0.2490024,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.2490024,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-27","depth":7,"bounds":{"left":0.087765954,"top":0.26496407,"width":0.027925532,"height":0.012769354},"on_screen":true,"value":"2026-04-27","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"28 Apr 2026 at 9:19","depth":7,"bounds":{"left":0.36569148,"top":0.26496407,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"339,8 MB","depth":7,"bounds":{"left":0.43351063,"top":0.26496407,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.26496407,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-25","depth":7,"bounds":{"left":0.087765954,"top":0.28092578,"width":0.02825798,"height":0.012769354},"on_screen":true,"value":"2026-04-25","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"26 Apr 2026 at 16:35","depth":7,"bounds":{"left":0.36569148,"top":0.28092578,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"39,7 MB","depth":7,"bounds":{"left":0.43650267,"top":0.28092578,"width":0.01861702,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.28092578,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-24","depth":7,"bounds":{"left":0.087765954,"top":0.29688746,"width":0.02825798,"height":0.012769354},"on_screen":true,"value":"2026-04-24","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"24 Apr 2026 at 22:30","depth":7,"bounds":{"left":0.36569148,"top":0.29688746,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"149,1 MB","depth":7,"bounds":{"left":0.43351063,"top":0.29688746,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.29688746,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-22","depth":7,"bounds":{"left":0.087765954,"top":0.31284916,"width":0.027925532,"height":0.012769354},"on_screen":true,"value":"2026-04-22","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"24 Apr 2026 at 12:08","depth":7,"bounds":{"left":0.36569148,"top":0.31284916,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"265,5 MB","depth":7,"bounds":{"left":0.43351063,"top":0.31284916,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.31284916,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-23","depth":7,"bounds":{"left":0.087765954,"top":0.32881084,"width":0.02825798,"height":0.012769354},"on_screen":true,"value":"2026-04-23","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"24 Apr 2026 at 12:07","depth":7,"bounds":{"left":0.36569148,"top":0.32881084,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"171,8 MB","depth":7,"bounds":{"left":0.43351063,"top":0.32881084,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.32881084,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-20","depth":7,"bounds":{"left":0.087765954,"top":0.34477255,"width":0.02825798,"height":0.012769354},"on_screen":true,"value":"2026-04-20","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"22 Apr 2026 at 18:44","depth":7,"bounds":{"left":0.36569148,"top":0.34477255,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"525,4 MB","depth":7,"bounds":{"left":0.43351063,"top":0.34477255,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.34477255,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-21","depth":7,"bounds":{"left":0.087765954,"top":0.36073422,"width":0.027593086,"height":0.012769354},"on_screen":true,"value":"2026-04-21","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"22 Apr 2026 at 9:16","depth":7,"bounds":{"left":0.36569148,"top":0.36073422,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"450,8 MB","depth":7,"bounds":{"left":0.43351063,"top":0.36073422,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.36073422,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-17","depth":7,"bounds":{"left":0.087765954,"top":0.37669593,"width":0.027260639,"height":0.012769354},"on_screen":true,"value":"2026-04-17","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"18 Apr 2026 at 13:35","depth":7,"bounds":{"left":0.36569148,"top":0.37669593,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"699,6 MB","depth":7,"bounds":{"left":0.43351063,"top":0.37669593,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.37669593,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-16","depth":7,"bounds":{"left":0.087765954,"top":0.3926576,"width":0.027593086,"height":0.012769354},"on_screen":true,"value":"2026-04-16","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"17 Apr 2026 at 8:57","depth":7,"bounds":{"left":0.36569148,"top":0.3926576,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"837,2 MB","depth":7,"bounds":{"left":0.43351063,"top":0.3926576,"width":0.021276595,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.3926576,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-15","depth":7,"bounds":{"left":0.087765954,"top":0.4086193,"width":0.027593086,"height":0.012769354},"on_screen":true,"value":"2026-04-15","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"16 Apr 2026 at 9:13","depth":7,"bounds":{"left":0.36569148,"top":0.4086193,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2,15 GB","depth":7,"bounds":{"left":0.4368351,"top":0.4086193,"width":0.017952127,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.4086193,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"2026-04-14","depth":7,"bounds":{"left":0.087765954,"top":0.424581,"width":0.027593086,"height":0.012769354},"on_screen":true,"value":"2026-04-14","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"15 Apr 2026 at 9:59","depth":7,"bounds":{"left":0.36569148,"top":0.424581,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1,09 GB","depth":7,"bounds":{"left":0.4368351,"top":0.424581,"width":0.017952127,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.424581,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe_sync_updated.sh","depth":7,"bounds":{"left":0.08277926,"top":0.4405427,"width":0.061835106,"height":0.012769354},"on_screen":true,"value":"screenpipe_sync_updated.sh","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 13:06","depth":7,"bounds":{"left":0.36569148,"top":0.4405427,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"20 KB","depth":7,"bounds":{"left":0.44115692,"top":0.4405427,"width":0.013630319,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Terminal scripts","depth":7,"bounds":{"left":0.4581117,"top":0.4405427,"width":0.03357713,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"archive.db-bak","depth":7,"bounds":{"left":0.08277926,"top":0.45650437,"width":0.03324468,"height":0.012769354},"on_screen":true,"value":"archive.db-bak","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Yesterday at 12:31","depth":7,"bounds":{"left":0.36569148,"top":0.45650437,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"11,13 GB","depth":7,"bounds":{"left":0.43417552,"top":0.45650437,"width":0.020611702,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"bounds":{"left":0.4581117,"top":0.45650437,"width":0.023603724,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"app","depth":7,"bounds":{"left":0.08277926,"top":0.47246608,"width":0.010305851,"height":0.012769354},"on_screen":true,"value":"app","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"26 Apr 2026 at 20:10","depth":7,"bounds":{"left":0.36569148,"top":0.47246608,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"193 KB","depth":7,"bounds":{"left":0.43849733,"top":0.47246608,"width":0.016289894,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.47246608,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"db.sqlite-wal","depth":7,"bounds":{"left":0.08277926,"top":0.4884278,"width":0.028922873,"height":0.012769354},"on_screen":true,"value":"db.sqlite-wal","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"26 Apr 2026 at 17:17","depth":7,"bounds":{"left":0.36569148,"top":0.4884278,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Zero bytes","depth":7,"bounds":{"left":0.43051863,"top":0.4884278,"width":0.024268618,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"bounds":{"left":0.4581117,"top":0.4884278,"width":0.023603724,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe_sync.sh","depth":7,"bounds":{"left":0.08277926,"top":0.50438946,"width":0.04288564,"height":0.012769354},"on_screen":true,"value":"screenpipe_sync.sh","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"18 Apr 2026 at 18:35","depth":7,"bounds":{"left":0.36569148,"top":0.50438946,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"15 KB","depth":7,"bounds":{"left":0.44115692,"top":0.50438946,"width":0.013630319,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Terminal scripts","depth":7,"bounds":{"left":0.4581117,"top":0.50438946,"width":0.03357713,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"app_settings.json","depth":7,"bounds":{"left":0.08277926,"top":0.5203512,"width":0.03856383,"height":0.012769354},"on_screen":true,"value":"app_settings.json","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"18 Apr 2026 at 17:42","depth":7,"bounds":{"left":0.36569148,"top":0.5203512,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"31 bytes","depth":7,"bounds":{"left":0.43583778,"top":0.5203512,"width":0.019281914,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JSON","depth":7,"bounds":{"left":0.4581117,"top":0.5203512,"width":0.012965426,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"screenpipe.db","depth":7,"bounds":{"left":0.08277926,"top":0.5363129,"width":0.03158245,"height":0.012769354},"on_screen":true,"value":"screenpipe.db","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"13 Apr 2026 at 17:21","depth":7,"bounds":{"left":0.36569148,"top":0.5363129,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Zero bytes","depth":7,"bounds":{"left":0.43051863,"top":0.5363129,"width":0.024268618,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Document","depth":7,"bounds":{"left":0.4581117,"top":0.5363129,"width":0.023603724,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"pipes","depth":7,"bounds":{"left":0.08277926,"top":0.5522745,"width":0.013630319,"height":0.012769354},"on_screen":true,"value":"pipes","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"11 Apr 2026 at 16:51","depth":7,"bounds":{"left":0.36569148,"top":0.5522745,"width":0.056848403,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"13 KB","depth":7,"bounds":{"left":0.44115692,"top":0.5522745,"width":0.013630319,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":7,"bounds":{"left":0.4581117,"top":0.5522745,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Name","depth":6,"bounds":{"left":0.07114362,"top":0.061452515,"width":0.29288563,"height":0.022346368},"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Date Modified","depth":6,"bounds":{"left":0.36402926,"top":0.061452515,"width":0.06017287,"height":0.022346368},"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Size","depth":6,"bounds":{"left":0.4242021,"top":0.061452515,"width":0.032247342,"height":0.022346368},"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Kind","depth":6,"bounds":{"left":0.45644948,"top":0.061452515,"width":0.040226065,"height":0.022346368},"on_screen":true,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"30 items, 1,94 TB available","depth":2,"bounds":{"left":0.25631648,"top":0.98324025,"width":0.051529255,"height":0.011173184},"on_screen":true,"automation_id":"_NS:34","role_description":"text"},{"role":"AXStaticText","text":"screenpipe","depth":1,"bounds":{"left":0.09906915,"top":0.019952115,"width":0.14378324,"height":0.0415004},"on_screen":true,"role_description":"text"}]...
|
6392771323366051413
|
-2647386114622522493
|
visual_change
|
accessibility
|
NULL
|
Favourites
jiminny
AirDrop
Recents
Applications
Do Favourites
jiminny
AirDrop
Recents
Applications
Documents
Downloads
lukas
iCloud
iCloud Drive
Sync folder
Locations
DXP4800PLUS-B5F
Eject
Network
Tags
CRM
Orange
Red
Yellow
Green
Blue
Purple
All Tags…
Name
Date Modified
Size
Kind
archive.db
Yesterday at 20:48
12,92 GB
Document
#recycle
Yesterday at 20:47
62,68 GB
Folder
db.sqlite-shm
Yesterday at 14:49
33 KB
Document
db.sqlite
Yesterday at 14:45
2,37 GB
Document
logs
Yesterday at 13:47
573 KB
Folder
sync.log
Yesterday at 13:47
7 KB
Log File
screenpipe.2026-05-07.0.log
7 May 2026 at 21:50
566 KB
Log File
data
Yesterday at 13:46
7,2 GB
Folder
2026-05-07
8 May 2026 at 9:26
305,6 MB
Folder
2026-05-06
6 May 2026 at 21:02
18,8 MB
Folder
2026-04-28
28 Apr 2026 at 22:23
166,7 MB
Folder
2026-04-27
28 Apr 2026 at 9:19
339,8 MB
Folder
2026-04-25
26 Apr 2026 at 16:35
39,7 MB
Folder
2026-04-24
24 Apr 2026 at 22:30
149,1 MB
Folder
2026-04-22
24 Apr 2026 at 12:08
265,5 MB
Folder
2026-04-23
24 Apr 2026 at 12:07
171,8 MB
Folder
2026-04-20
22 Apr 2026 at 18:44
525,4 MB
Folder
2026-04-21
22 Apr 2026 at 9:16
450,8 MB
Folder
2026-04-17
18 Apr 2026 at 13:35
699,6 MB
Folder
2026-04-16
17 Apr 2026 at 8:57
837,2 MB
Folder
2026-04-15
16 Apr 2026 at 9:13
2,15 GB
Folder
2026-04-14
15 Apr 2026 at 9:59
1,09 GB
Folder
screenpipe_sync_updated.sh
Yesterday at 13:06
20 KB
Terminal scripts
archive.db-bak
Yesterday at 12:31
11,13 GB
Document
app
26 Apr 2026 at 20:10
193 KB
Folder
db.sqlite-wal
26 Apr 2026 at 17:17
Zero bytes
Document
screenpipe_sync.sh
18 Apr 2026 at 18:35
15 KB
Terminal scripts
app_settings.json
18 Apr 2026 at 17:42
31 bytes
JSON
screenpipe.db
13 Apr 2026 at 17:21
Zero bytes
Document
pipes
11 Apr 2026 at 16:51
13 KB
Folder
Name
Date Modified
Size
Kind
30 items, 1,94 TB available
screenpipe...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16778
|
748
|
18
|
2026-05-11T09:23:18.917526+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491398917_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
9040864768715818701
|
-8096329468753105982
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
FinderFileEditViewGoWindowHelp•00DEV (docker)DOCKERO ₴1DEV (docker)882APP (-zsh)|• жзmasterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]>0.-zshlabol• Support Daily • in 2h 37 m84-zsh885100%8• Mon 11 May 12:23:18181screenpipe"0 ₴6DEV...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16779
|
749
|
19
|
2026-05-11T09:23:18.892898+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491398892_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4432128530553528889
|
-8096333883715245118
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
FinderFavouritesE jiminny• AirDrop© Recents* Applications9 Documents© Downloadsi lukas• iCloud Drive999 Sunc tolderL DXP4800PLUS-Bor® NetworkO CRM• OrangeRed•Yellow• Greero Bue• Purple• All Tags..conVIeWWindowmelpscreenoipe• archive.db> #recycledb.sqlite-shmdb.sqlitevi logsB sync.log• screenpipe.2026-05-07.0.1ogv data> 2026-05-072026-05-01>2026-04-282026-04-27> 2026-04-25•2026-04-242026-04-222026-04-232026-04-20• 2026.04.212026-04-172026-04-16• 2026-04-15>2026-04-14screenpipe_sync_updated.sharchive. db-oak>?appdb.salite-walscreenpipe_sync.shapp_settings.json• screenpipe.db›_pipesGroupsearchShare Edit TagsActionDate ModifiedYesterday at 20:48Yesterday at 20:47Yesterday at 14:49Yesterday at 14:45Yesterday at 13:47Yesterday at 13:477 May 2026 at 21:50Yesterday at 13:468 May 2026 at 9:266 May 2026 at 21:0228 Apr 2026 at 22:2328 Apr 2026 at 9:1926 Apr 2026 at 16:3524 Aor 2026 at 22:3024 Apr 2026 at 12:0824 Apr 2026 at 12:0722 Apr 2026 at 18:4422 Apr 2026 at 9:1618 Apr 2026 at 13:351/ Apr 2026 at 8.0/16 Apr 2026 at 9:13PSPhpStorm30 items. 1.94 TB available12,92 GB Document62,68 GBFolder33 KB Document2,37 GB Document573 KB7 KB Log File566 KB Loa File7,2 GB Folder305,6 MBFolder18,8 MB Folder166,7 MB Folder339,8 MB39,7 MBrolder149.1 MB Folden265,5 MB171,8 MB525.4 MB450,8 MB Folder83/.2MBrolder2,15 GBFolder$1$0helravountesE jiminny• Recents* Application© Documents• Downloads.G lukasIcloud• iCloud Drive288 Sync folderOO DXP4800PLUS-B5F A® NetworkworkNamev 2026= PLanhat Petko interest event 2026-05-11.mp4= Dally 2026-05-11.mp4iai Daily 2026-05-08.mp4* 1-1 2026-05-07.mp4Daily 2026-05-07.mp4шгN 1-1 2026-04-24,m04= Daily 2026-04-24.mp4e User Pilot introduction Adi 2026-04-23.mp4Daily 2026-04-23.mp4Daily 2026-04-22.mp4Refinement 2026-04-06.mp4Dally 2026-04-21.mp4Da Refinement 2026-04-20.mp4Daily 2026-04-20.mp4Daily 2026-04-17.mp4Tu Daily 2026-04-16.mp4as Planning 2026-04-15.mp4Retro 2026-04-14.mp4Daily 2026-04-14.mp4• User pilot (Adi) 2026-04-09.mp4= Dallv 2026-03-24.mo4• Refinement 2026-03-23.mp4** BE chapter 2026-03-20.mp4• Dailv 2026-03-20.mo4sn Planing 2026-03-18-converted.mp4Refinement 2026-02-09-converted.mp4кя Dallv 2026-03-19,mo4E Peview 2026-02-19 mn/Planing 2026-03-18.mp4ketro 2020-03-1/.moc• Dailv 2026-03-17 mn/- Refinement 2026-03-16.mp4Daily 2026-03-16.mp4Dailv 2026-03-13.mo4г 1.1 2026.02.12 mл/aa Daily 2026-03-11.mp4• Daily 2026-03-10 mn/*: Refinement 2026-03-09.mp4• Daily 2026-03-09.mp4ru Daily 2026-03-06.mo4a: Plannina 2026-03-04.mo4Support Daily - in 2h 37m100% 2Q SearDate ModifiedToday at 12:23Today at 10:028 Mav 2026 at 10:227 May 2026 at 18:217 May 2026 at 10:1024 Aor 2026 at 14:4424 Apr 2026 at 10:1123 Apr 2026 at 11:5823 Apr 2026 at 10:3222 Apr 2026 at 10:2121 Apr 2026 at 10:0020 Aor 2026 at 16:5620 Apr 2026 at 10:0617 Apr 2026 at 10:1616 Aor 2026 at 10:0015 Apr 2026 at 11:1414 Apr 2026 at 10:09O Anr 2026 at 14:479 Apr 2026 at 10:078 Apr 2026 at 10:137Aor 2026 at 10:016 Apr 2026 at 10:081 Apr 2026 at 12-2021 Mar 2026 at 18:2031 Mar 2026 at 10:1030 Mar 2026 at 17:1230 Mar 2026 at 10:0527 Mar 2026 at 10:0924 Mar 2026 at 10:0022 Mar 2026 at 17:0223 Mar 2026 at 10:0020 Mar 2026 at 11:4620 Mar 2026 at 10:0619 Mar 2026 at 12:0119 Mar 2026 at 9:5719 Mar 2026 at 16:2017 Mar 2026 at 17:4017 Mar 2026 at 10:18.16 Mar 2026 at 16:5516 Mar 2026 at 10:0213 Mar 2026 at 10:1212 Mar 2026 at 19:2611 Mar 2026 at 10:0610 Mar 2026 at 9:579 Mar 2026 at 17:046 Mar 2026 at 9:578• Mon 11 May 12:23:18491,3 MBMPEG-4 movie1.37 GEMPEG-4 movie1,55 GB MPEG-4 movie931,7 MB MPEG-4 movie1.86 G:MPEG-4 movie832,2 MB MPEG-4 movie724 MB1.74 GEMPEG-4 movie1,36 GB MPEG-4 movie2,41 GB567,8 MBMPEG-4 movie4.25 GBMPEG-4 movie698,5 MB MPEG-4 movie1,16 GB513.4 MBMPEG-4 movie2,75 GB MPEG-4 movie1,44 GB924,4 MBMPEG-4 movie262 6 MRMDEG-A movie748,8 MB MPEG-4 movie1,04 GBMPEG-4 movie575.5 M:720,5 MB MPEG-4 movie1,02 GB4,68 GBMPEG-4 movie2AGRMDEG-A movie923,6 MB MPEG-4 movie2,77 GB641.8 MEMPEG-4 movie884,3 MB MPEG-4 movie476,6 MB550.8 M3MPEG-4 movie2AAGRMDEG-A movid438,9 MBMPEG-4 movie1,68 GBMPEG-4 movie430.4 ME2,38 GB MPEG-4 movie2,26 GB386.3 MBMPEG-4 movie705,8 MBMDSG-A movie2,78 GB MPEG-4 movie1,53 GB1.2 GEMPEG.A movid4,19 GB MPEG-4 movie592,2 MB1.02 GEMPEG-4 movie637,6 MBMDEG.A movid978,7 MBMPEG-4 movie798,7 MBMPEG-4 movie404.6 MPMPEG-A movie4,16 GB MPEG-4 movie319,7 MB291.7 MEMPEG-4 movieAen Cp MoRe A mavid1 of 154 selected, 1,94 TB available...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16780
|
749
|
20
|
2026-05-11T09:23:20.800416+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491400800_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42885637,"top":0.09736632,"width":0.5711436,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42885637,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42885637,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42885637,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42885637,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42885637,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42885637,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42885637,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42885637,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42885637,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42885637,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42885637,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42885637,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42885637,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42885637,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42885637,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42885637,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42885637,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42885637,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42885637,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42885637,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42885637,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42885637,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42885637,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42885637,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16779
|
NULL
|
NULL
|
NULL
|
|
16781
|
748
|
19
|
2026-05-11T09:23:33.734207+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491413734_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9036216452231894488
|
-4259980196618770484
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project...
|
16778
|
NULL
|
NULL
|
NULL
|
|
16782
|
749
|
21
|
2026-05-11T09:23:33.739547+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491413739_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42885637,"top":0.09736632,"width":0.5711436,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42885637,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42885637,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42885637,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42885637,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42885637,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42885637,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42885637,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42885637,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42885637,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42885637,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42885637,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42885637,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42885637,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42885637,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42885637,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42885637,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42885637,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42885637,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42885637,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42885637,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42885637,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42885637,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42885637,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42885637,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
4283169546787192088
|
-4256602496898242612
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16783
|
748
|
20
|
2026-05-11T09:24:04.120982+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491444120_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16784
|
749
|
22
|
2026-05-11T09:24:04.209974+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491444209_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16782
|
NULL
|
NULL
|
NULL
|
|
16785
|
748
|
21
|
2026-05-11T09:24:34.500878+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491474500_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16786
|
749
|
23
|
2026-05-11T09:24:34.611025+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491474611_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16787
|
748
|
22
|
2026-05-11T09:25:04.860963+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491504860_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16788
|
749
|
24
|
2026-05-11T09:25:05.011567+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491505011_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16786
|
NULL
|
NULL
|
NULL
|
|
16789
|
NULL
|
0
|
2026-05-11T09:25:35.200642+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491535200_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16790
|
NULL
|
0
|
2026-05-11T09:25:35.388824+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491535388_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16786
|
NULL
|
NULL
|
NULL
|
|
16791
|
750
|
0
|
2026-05-11T09:26:05.556077+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491565556_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16792
|
751
|
0
|
2026-05-11T09:26:16.026249+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491576026_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16786
|
NULL
|
NULL
|
NULL
|
|
16793
|
750
|
1
|
2026-05-11T09:26:35.889472+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491595889_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16794
|
751
|
1
|
2026-05-11T09:26:52.350505+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491612350_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16786
|
NULL
|
NULL
|
NULL
|
|
16795
|
750
|
2
|
2026-05-11T09:27:12.679880+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491632679_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16796
|
751
|
2
|
2026-05-11T09:27:37.752205+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491657752_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7243301135459085779
|
-4562776902396090420
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}...
|
16786
|
NULL
|
NULL
|
NULL
|
|
16797
|
751
|
3
|
2026-05-11T09:27:41.911609+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491661911_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
800368925815497634
|
-4547014303566075956
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16798
|
750
|
3
|
2026-05-11T09:27:41.927010+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491661927_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_expanded":false}]...
|
-9140679751598560133
|
-8132362818571490362
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
FinderFileEditViewGoWindowHelp<•00DEV (docker)DOCKERO ₴1DEV (docker)882APP (-zsh)|• жзmasterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]• Support Daily • in 2h 33 m-zsh84-zsh885100% <78• Mon 11 May 12:27:41181screenpipe"0 ₴6DEV...
|
16783
|
NULL
|
NULL
|
NULL
|
|
16799
|
751
|
4
|
2026-05-11T09:28:12.541582+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491692541_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16797
|
NULL
|
NULL
|
NULL
|
|
16800
|
750
|
4
|
2026-05-11T09:28:12.839584+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491692839_m1.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3876230358461125011
|
-4259980196618770484
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16801
|
751
|
5
|
2026-05-11T09:28:26.037122+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491706037_m2.jpg...
|
PhpStorm
|
faVsco.js – PlaybackController.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37699467,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\PlaybackPage\\Download\\Services\\DownloadActivityService;\nuse Jiminny\\Http\\Serializers\\JsonSerializer;\nuse Jiminny\\Http\\Transformers\\PlaybackPageTransformer;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Track;\nuse Jiminny\\Services\\PlanhatService;\nuse Jiminny\\Services\\PlaybackService;\nuse JsonException;\nuse Spatie\\Fractal\\Fractal;\nuse Illuminate\\Support\\Facades\\Cookie;\n\nfinal class PlaybackController extends FrontendController\n{\n use AuthorizesRequests;\n\n public function __construct(\n private readonly PlaybackService $playbackService,\n private readonly DownloadActivityService $downloadActivityService,\n private readonly PlanhatService $planhatService,\n ) {\n }\n\n /**\n * @throws AuthorizationException\n * @throws JsonException\n */\n public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string\n {\n $this->authorize('view', $activity);\n\n /** @var User $user */\n $user = $request->user();\n\n $activityTypeCheck = in_array(\n $activity->type,\n [\n Activity::TYPE_CONFERENCE,\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n ],\n true\n );\n\n abort_unless($activityTypeCheck, 404);\n\n $notificationId = $request->input('nId');\n if ($notificationId) {\n /** @var DatabaseNotification|null $notification */\n $notification = $user->unreadNotifications->where('id', $notificationId)->first();\n\n if ($notification) {\n $notification->markAsRead();\n }\n }\n\n $view = $request->input('view', 'page');\n\n $activity->loadMissing([\n 'questions.participant',\n 'participants.activity',\n 'topicTriggers',\n 'topicTriggers.participant',\n 'topicTriggers.playbackThemeTopicTrigger',\n 'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',\n ]);\n\n $data = Fractal::create()\n ->item(\n $activity,\n $transformer->setConsumer($user)\n )\n ->serializeWith(new JsonSerializer())\n ->toArray();\n\n $data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);\n\n if (! isset($data['playbackData']['tracks'])) {\n $data['playbackData']['tracks'] = [];\n }\n\n /**\n * Sending 'playbackVisited' event to Planhat without slowing the\n * response to the user e.g. after the response is sent back.\n */\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'playbackVisited',\n payload: [\n 'activityId' => $activity->getId(),\n 'activityUuid' => $activity->getUuid(),\n ]\n )\n )->always();\n\n return $this->render([\n 'playbackData' => [\n 'activity' => $data['playbackData'],\n 'favorited' => $data['favorited'],\n 'subscribed' => $data['subscribed'],\n 'view' => $view,\n ],\n ]);\n }\n\n private function getPreloadedPlaylist(Activity $activity): array\n {\n $masterPlaylist = [];\n $urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;\n\n $this->authorize('stream', $activity);\n\n $masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);\n $masterPlaylist['placeholder'] = $urlPlaceholder;\n $masterPlaylist['tracks'] = [];\n\n /** @var Models\\Track $track */\n foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {\n $mediaPlaylistPath = $this->mediaPlaylistPath($track);\n $masterPlaylist['tracks'][] = [\n 'id' => $track->getUuid(),\n 'path' => $mediaPlaylistPath,\n ];\n }\n\n return $masterPlaylist;\n }\n\n /**\n * @throws AuthorizationException\n */\n public function playlist(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);\n\n return response($masterPlaylist)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n /**\n * Generate a VTT \"Video Text Tracks\" file.\n *\n * @throws AuthorizationException\n */\n public function vtt(Activity $activity): Response\n {\n $this->authorize('stream', $activity);\n\n $vtt = $this->playbackService->generateVtt($activity);\n\n return response($vtt)\n ->header('Content-Type', 'text/vtt;charset=utf-8');\n }\n\n /**\n * @throws AuthorizationException\n */\n public function media(Track $track): Response\n {\n $this->authorize('stream', $track->activity);\n\n $this->queueMediaCookies($track);\n\n $payload = $this->playbackService->generateMediaPlaylist($track);\n\n return response($payload)\n ->header('Content-Type', 'application/x-mpegURL');\n }\n\n private function mediaPlaylistPath(Track $track): string\n {\n $this->queueMediaCookies($track);\n\n // @TODO return cdn when CORS is fixed\n // return client_cdn($track->content_path, $track->activity->user->team);\n return route('media', ['track' => $track->id_string]);\n }\n\n private function queueMediaCookies(Track $track): void\n {\n $keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;\n if (Cookie::has($keepAliveCookieName)) {\n return;\n }\n\n // Restrict segment URLs to the IP requesting it.\n $remoteIp = request()->ip();\n $cookies = $this->playbackService->generateCookies($track, $remoteIp);\n\n $keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;\n\n // Cookie is only valid for this particular stream path.\n $trackPath = '/' . preg_replace('/\\/[^\\/]+$/', '/', $track->content_path);\n $host = config('jiminny.client_cdn_signed_cookie_domain');\n\n // Queue up cookies to be able to be served secure track media.\n foreach ($cookies as $name => $cookie) {\n Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);\n }\n\n // Cookie is only valid for this particular activity.\n $paths = [\n route('activity.playback', $track->activity->id_string, false),\n route('media', ['track' => $track->id_string], false),\n ];\n foreach ($paths as $path) {\n Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);\n }\n }\n\n /**\n * Used by the Web app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function download(Activity $activity): RedirectResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Download failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return redirect($url);\n }\n\n /**\n * Used by the Mobile app to download the activity.\n *\n * @throws AuthorizationException\n */\n public function getDownloadUrl(Activity $activity): JsonResponse\n {\n $this->authorize('download', $activity);\n\n try {\n $url = $this->downloadActivityService->generateDownloadUrl($activity);\n } catch (\\Throwable $e) {\n Log::info(\n __METHOD__ . ' Getting signed url failed.',\n ['activity' => $activity->getUuid(), 'message' => $e->getMessage()]\n );\n abort(404, $e->getMessage());\n }\n\n return new JsonResponse(\n ['activity_url' => $url],\n JsonResponse::HTTP_OK\n );\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
4283169546787192088
|
-4256602496898242612
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
3
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\PlaybackPage\Download\Services\DownloadActivityService;
use Jiminny\Http\Serializers\JsonSerializer;
use Jiminny\Http\Transformers\PlaybackPageTransformer;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Models\Activity;
use Jiminny\Models\Track;
use Jiminny\Services\PlanhatService;
use Jiminny\Services\PlaybackService;
use JsonException;
use Spatie\Fractal\Fractal;
use Illuminate\Support\Facades\Cookie;
final class PlaybackController extends FrontendController
{
use AuthorizesRequests;
public function __construct(
private readonly PlaybackService $playbackService,
private readonly DownloadActivityService $downloadActivityService,
private readonly PlanhatService $planhatService,
) {
}
/**
* @throws AuthorizationException
* @throws JsonException
*/
public function show(Activity $activity, PlaybackPageTransformer $transformer, Request $request): array|string
{
$this->authorize('view', $activity);
/** @var User $user */
$user = $request->user();
$activityTypeCheck = in_array(
$activity->type,
[
Activity::TYPE_CONFERENCE,
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
],
true
);
abort_unless($activityTypeCheck, 404);
$notificationId = $request->input('nId');
if ($notificationId) {
/** @var DatabaseNotification|null $notification */
$notification = $user->unreadNotifications->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
$view = $request->input('view', 'page');
$activity->loadMissing([
'questions.participant',
'participants.activity',
'topicTriggers',
'topicTriggers.participant',
'topicTriggers.playbackThemeTopicTrigger',
'topicTriggers.playbackThemeTopicTrigger.playbackThemeTopic',
]);
$data = Fractal::create()
->item(
$activity,
$transformer->setConsumer($user)
)
->serializeWith(new JsonSerializer())
->toArray();
$data['playbackData']['masterPlaylist'] = $this->getPreloadedPlaylist($activity);
if (! isset($data['playbackData']['tracks'])) {
$data['playbackData']['tracks'] = [];
}
/**
* Sending 'playbackVisited' event to Planhat without slowing the
* response to the user e.g. after the response is sent back.
*/
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'playbackVisited',
payload: [
'activityId' => $activity->getId(),
'activityUuid' => $activity->getUuid(),
]
)
)->always();
return $this->render([
'playbackData' => [
'activity' => $data['playbackData'],
'favorited' => $data['favorited'],
'subscribed' => $data['subscribed'],
'view' => $view,
],
]);
}
private function getPreloadedPlaylist(Activity $activity): array
{
$masterPlaylist = [];
$urlPlaceholder = PlaybackService::M3U8_TRACK_PLACEHOLDER;
$this->authorize('stream', $activity);
$masterPlaylist['m3u8'] = $this->playbackService->generateMasterPlaylist($activity, null, $urlPlaceholder);
$masterPlaylist['placeholder'] = $urlPlaceholder;
$masterPlaylist['tracks'] = [];
/** @var Models\Track $track */
foreach ($this->playbackService->getMasterPlaylistTracks($activity) as $track) {
$mediaPlaylistPath = $this->mediaPlaylistPath($track);
$masterPlaylist['tracks'][] = [
'id' => $track->getUuid(),
'path' => $mediaPlaylistPath,
];
}
return $masterPlaylist;
}
/**
* @throws AuthorizationException
*/
public function playlist(Activity $activity): Response
{
$this->authorize('stream', $activity);
$masterPlaylist = $this->playbackService->generateMasterPlaylist($activity);
return response($masterPlaylist)
->header('Content-Type', 'application/x-mpegURL');
}
/**
* Generate a VTT "Video Text Tracks" file.
*
* @throws AuthorizationException
*/
public function vtt(Activity $activity): Response
{
$this->authorize('stream', $activity);
$vtt = $this->playbackService->generateVtt($activity);
return response($vtt)
->header('Content-Type', 'text/vtt;charset=utf-8');
}
/**
* @throws AuthorizationException
*/
public function media(Track $track): Response
{
$this->authorize('stream', $track->activity);
$this->queueMediaCookies($track);
$payload = $this->playbackService->generateMediaPlaylist($track);
return response($payload)
->header('Content-Type', 'application/x-mpegURL');
}
private function mediaPlaylistPath(Track $track): string
{
$this->queueMediaCookies($track);
// @TODO return cdn when CORS is fixed
// return client_cdn($track->content_path, $track->activity->user->team);
return route('media', ['track' => $track->id_string]);
}
private function queueMediaCookies(Track $track): void
{
$keepAliveCookieName = 'Media-KeepAlive_' . $track->id_string;
if (Cookie::has($keepAliveCookieName)) {
return;
}
// Restrict segment URLs to the IP requesting it.
$remoteIp = request()->ip();
$cookies = $this->playbackService->generateCookies($track, $remoteIp);
$keepAliveDuration = PlaybackService::MEDIA_COOKIE_MINIMUM_DURATION / 60;
// Cookie is only valid for this particular stream path.
$trackPath = '/' . preg_replace('/\/[^\/]+$/', '/', $track->content_path);
$host = config('jiminny.client_cdn_signed_cookie_domain');
// Queue up cookies to be able to be served secure track media.
foreach ($cookies as $name => $cookie) {
Cookie::queue($name, $cookie, $keepAliveDuration, $trackPath, $host, true, true);
}
// Cookie is only valid for this particular activity.
$paths = [
route('activity.playback', $track->activity->id_string, false),
route('media', ['track' => $track->id_string], false),
];
foreach ($paths as $path) {
Cookie::queue($keepAliveCookieName, 1, $keepAliveDuration, $path, $host, true, true);
}
}
/**
* Used by the Web app to download the activity.
*
* @throws AuthorizationException
*/
public function download(Activity $activity): RedirectResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Download failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return redirect($url);
}
/**
* Used by the Mobile app to download the activity.
*
* @throws AuthorizationException
*/
public function getDownloadUrl(Activity $activity): JsonResponse
{
$this->authorize('download', $activity);
try {
$url = $this->downloadActivityService->generateDownloadUrl($activity);
} catch (\Throwable $e) {
Log::info(
__METHOD__ . ' Getting signed url failed.',
['activity' => $activity->getUuid(), 'message' => $e->getMessage()]
);
abort(404, $e->getMessage());
}
return new JsonResponse(
['activity_url' => $url],
JsonResponse::HTTP_OK
);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16802
|
750
|
5
|
2026-05-11T09:28:38.317483+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491718317_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FinderFileEditViewGoWindowHelp•00DEV (docker)DOCKE FinderFileEditViewGoWindowHelp•00DEV (docker)DOCKERO ₴1DEV (docker)882APP (-zsh)• жзmasterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]>0.-zshlabolSupport Daily - in 2h 32 m84-zsh885100%8• Mon 11 May 12:28:37181ffmpeg0 ₴6DEV...
|
NULL
|
7470588269586113629
|
NULL
|
click
|
ocr
|
NULL
|
FinderFileEditViewGoWindowHelp•00DEV (docker)DOCKE FinderFileEditViewGoWindowHelp•00DEV (docker)DOCKERO ₴1DEV (docker)882APP (-zsh)• жзmasterJY-20818-move-AJ-reports-to-separated-datadog-metricJY-20773-fix-automated-reports-user-pilot-trackingJY-20157-AJ-report-not-send-notificationJY-20508-notify-before-AJ-report-expirationJY-20372-ai-reports-promotion-pagesJY-20352-sync-opportunities-without-a-local-owner-user-id-is-nullJY-20738-debug-AJ-tracking-UPJY-18909-automated-reports-ask-jiminnyJY-20692-fix-integration-app-[API_KEY]@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ devroot@docker_lamp_1:/home/jiminny# ]>0.-zshlabolSupport Daily - in 2h 32 m84-zsh885100%8• Mon 11 May 12:28:37181ffmpeg0 ₴6DEV...
|
16800
|
NULL
|
NULL
|
NULL
|
|
16803
|
751
|
6
|
2026-05-11T09:28:38.350+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491718350_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5641617897080429754
|
-8160223333407913180
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
PhostormVIewINavicarecodeFV faVsco.js°9 JY-20725-handle-HS-search-rate-lProiectC) SubscrintionContro• synckelatedAcuivilymanager.pnpC) TeamA Automationweinteceinixeereit@ TeamController.phpc) ToaminciahtcContrС) Cпескапакеtгукemotematch.phg( RateLimitException.phpo transcrlpuonconu© TranslationControll© UserController.php© VocabularyControll>@ AuthC) Client.phpphpidehelper.ph©) PaqinationState.phoC) MatchCrmData.php© CrmObiectsResolver.phcC) ProviderRateLimiter.php©) PaqinationConfia.phpfinal class PlavbackController extends FrontendcontrollenMA6X3AY-customerapl›J InternaD Kioskeams© ActivityController.pAutomatedReportsic) DashboardControllec) ImpersonationContC) MediaPinelinecontc) Orcanizationscontr0 PartnersController.C) ProfileController.oh© SearchController.pt 1ac>• SettingsM Telerhonvv M Wehhook>D Hubspot>D IntegrationAppSub: 10cl© ActivityProviderCor 104© ActivityTranscriptio 107© BaseController.php 10d© CalendarController. 109© ReportController.ph 11dSoftphoneWebhool 111(c. AbstractController.phg 11g® CommentContextinter 113© ConterencesOptinouts 114c) controller.phpExportController.php© FrontendController.ph| 112T FrontendControllerTra 118% GeocodingController.p 119(C) HealthCheckControlleiP LiveCoachController.pl 120© MissingTeamControlle 120(C) MobileController.ohv© NotificationController.1 1z4© NotificationProviderCo 145Ye) PlavbackControlier ohg DlavlistController nhn1A4IO Spublic function show Activity Sactivity, PlaybackPageTransformer $transformer, Request Srequest): arraylstr:Sdata[ 'playbackData']['masterPlaylist'] = Sthis->qetPreloadedPlavlist(Sactivity):if (! isset($data['playbackData']['tracks'D)) ?ScatalolavbackData'"tracks'= ?* Sending 'playbackVisited' event to Planhat without slowing the* response to the user e.g. after the response is sent backdofen dfn () => $this->planhatService->track(user: Suser,event: 'playbackVisited',payload: ['activityId' => Sactivity->getIdO'activityUuid' => Sactivity->getUuid.) ->always O:return Sthis->render(0"playbackData' =>'activity' => Sdatal'playbackbata''tavorited' => Sdatal'favorited'n'subscribed' => $datal 'subscribed'1Vlew => svlew.private function getPreloadedPlaylist(Activity Sactivity): arrayt...}Othrows AuthorizationEycentionGET Inlavlict[activitv) m2u8 fnlavlict1public function playlist(Activity $activity): Response{...}(C) PucherController nhng SlackController nhnlelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (todav Q•08)A SF (jiminny@localhost]4 HS_local (jiminny@localhost]# console [PKob.# console leu)# console [slAGiNg)[2026-05-07 14:21:15] local.INF0: [Hubspot] DEBUG Getting headers {MMi9.У'Date":["Thu,07 May 2026 14:21:15 GMT"]."concenc-lyoe. apolicacion/ison.charser=utt-on"Transter-Encodinq":"chunked")n"CF-Ray":["9f80deb8db60dc3a-SOF"]"CF-Cache-Status": L"DYNAMIC"J,"Strict-Transport-Security":L"max-age=31536008; incLudesubbomains; preload"J."access-control-allow-credentials": "false"."server-timing": ["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3)",cfr;desc=|"9f80deb8e7c6dc3a-IAD\""]"x-content-tvoe-ootions":""nosniff""x-hubsoot-correlation-id":"019e02d0-6fd8-7812-bdba-885b7ccb3ee3"])ISot-Cookie"•" c+hm-Stlirtd0aXVr.kSAndas6hzVVKhzTn0BidvMaheCtmoV-1778163675-1.0.1107-May-26 14:51:15 GMT; domain=.hubapi.com; Http0nly; Secure; SameSite=None"],"Report-To":["{\"endpoints)":[{("url\":"https:|/\V/a.nel.cloudflare.com\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RV\"group)":\"cf-nel\",("max_age\":604800}"],"NEL":["{("success_fraction\":0.01,reportto. "cr-nel,"max agel ":604800}"].'Server": ["cloudflare"]}} {'correlation_10":"95256555-ec98-4541-b9za-adta/sboYeab",'trace_10":"C/AD8565-905t-4604-9405-0e5b551e5545S0 hl• supoort Dally • In Zn 32m100% 47. • Mon 11 May 12:28:38AskJiminnyReportActivityServiceTestvCascadeHubspot Rate Limit Re+0 ..Run only 5 workers for analytics low so concurrency naturally matches the 5 RPS limit:bashwphp artisan queue:work --queue=analvtics low -tries=0 --max-1obs=0 --processes=5Effect: 5 workers x 1 successful op/sec = 5/sec. No rate-limit storms because we're under the limit by design. Each worker rarely sees a 429.Trade-ors.*Slower throughput (still o/sec celling = o.o hours for 100k)• Other ioos on this queue comoete with the bottlleneckWorks, but requires Horizon config changesF. Pre-flight cache check at job entry (mentioned earlier)Eliminates the DB transaction churn from cache-hit cvcles. Doesn't change the throughput ceiling, but reduces cost durina a storm to almost nothinalEasy win. Pairs with all other optionsG. Fllter unmatchable activities berore dispatchIf manv activities have no email/ohone/name to match. dispatchina them is pure waste.O phpActivitv..needsCrmMatchol›whereHas('participants', fn ($q) => $q->whereNotNull('email")->orwhereNotNu((phone))→each(fn (($a) = MatchActivitvcrmData::dispatchSa->id)):If 30% of the 100k are unmatchable (no contact info), removing them gets you to 70k - still over 9k cap, but closer.Recommended combination1. A batch endnoints) = 100x throudhout. Solves the oroblem2. B (dispatch throttling) - defense in depth, prevents future storms.3. C (ookuo cachina) — reduces redundant Ap calls)4. F (pre-flight cache check) - eliminates DB churn during inevitable stormsWith A + B alone, the 100k 9k problem becomes 100k → 100k completed in ~3-10 minutes, depending on caching efficiency.A is the leverage point. Without batch endpoints, you're brute-forcing through a 5 RPS hose. With them, the hose is 500 matches/sec.SummaryRace window: 5-10 wasted real APl calls per cache - cycle. Bounded, ~15k calls wasted in 30 min (vs 441k without cache). Acceptable, can be tightened with aprobe lock if neededHubSpot-named middleware on multi-CRM iob: no functional issue for HubSpot. Salesforce/Pipedrive rate-limits ao through the wrona path (treated as reallexceptions). Rename + extend other clients for consistency.100k → 9k bottleneck: not a rate-limiter problem, an architectural one. The fix is batch endpoints + dispatch throttling, not more aggressive retry logic..nl .ok what about÷ « CodeClaude Onus 4.7 MediumWN Windsurf Teams101-20UTF.8io 4 spaces...
|
16801
|
NULL
|
NULL
|
NULL
|
|
16804
|
750
|
6
|
2026-05-11T09:28:43.289475+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491723289_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16805
|
751
|
7
|
2026-05-11T09:28:43.285903+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491723285_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"bounds":{"left":0.11968085,"top":0.0,"width":0.31881648,"height":1.0},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16806
|
751
|
8
|
2026-05-11T09:28:46.866852+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491726866_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8515521135329858963
|
-2489294891750393478
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All...
|
16805
|
NULL
|
NULL
|
NULL
|
|
16807
|
751
|
9
|
2026-05-11T09:28:48.999040+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491728999_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
16808
|
750
|
7
|
2026-05-11T09:29:29.732318+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491769732_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16804
|
NULL
|
NULL
|
NULL
|
|
16809
|
751
|
10
|
2026-05-11T09:29:29.749419+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491769749_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16807
|
NULL
|
NULL
|
NULL
|
|
16810
|
750
|
8
|
2026-05-11T09:30:00.028871+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491800028_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16804
|
NULL
|
NULL
|
NULL
|
|
16811
|
751
|
11
|
2026-05-11T09:30:00.105470+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491800105_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16807
|
NULL
|
NULL
|
NULL
|
|
16812
|
NULL
|
0
|
2026-05-11T09:30:30.349105+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491830349_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16804
|
NULL
|
NULL
|
NULL
|
|
16813
|
NULL
|
0
|
2026-05-11T09:30:30.450767+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491830450_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16807
|
NULL
|
NULL
|
NULL
|
|
16814
|
752
|
0
|
2026-05-11T09:31:00.697959+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491860697_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16804
|
NULL
|
NULL
|
NULL
|
|
16815
|
753
|
0
|
2026-05-11T09:31:00.839349+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778491860839_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.38696808,"top":0.22426178,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.22266561,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.22266561,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42918882,"top":0.09736632,"width":0.57081115,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42918882,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42918882,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42918882,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42918882,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42918882,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42918882,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42918882,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42918882,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42918882,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42918882,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42918882,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42918882,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42918882,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42918882,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42918882,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42918882,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42918882,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42918882,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42918882,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42918882,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42918882,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42918882,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42918882,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42918882,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3191170991068695078
|
-2489294891750393478
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
16807
|
NULL
|
NULL
|
NULL
|