six-degrees 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +3 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +14 -14
- package/.next/standalone/.next/routes-manifest.json +18 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +26 -18
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +26 -18
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +25 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +2 -2
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +2 -2
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +2 -2
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +3 -3
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -3
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +2 -2
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/backup/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/data/backup/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/data/backup/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/data/backup/route.js +7 -0
- package/.next/standalone/.next/server/app/api/data/backup/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/data/backup/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/data/csv/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/data/csv/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/data/csv/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/data/csv/route.js +8 -0
- package/.next/standalone/.next/server/app/api/data/csv/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/data/csv/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/data/export/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restore/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/data/restore/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/data/restore/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/data/restore/route.js +7 -0
- package/.next/standalone/.next/server/app/api/data/restore/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/data/restore/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/data/restore/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/data/reveal/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +2 -2
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +2 -2
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +2 -2
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +2 -2
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +4 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +3 -3
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +3 -3
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +3 -3
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +2 -2
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +2 -2
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +2 -2
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +2 -2
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +2 -2
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +32 -24
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +29 -22
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +20 -15
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +29 -22
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +32 -24
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +32 -24
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +21 -18
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +32 -24
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +32 -24
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +20 -18
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +32 -24
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +32 -24
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +20 -18
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +32 -24
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +21 -18
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +32 -24
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__15wc9e-._.js → [root-of-the-server]__063s0x7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__066rab1._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__13dx4sd._.js → [root-of-the-server]__0_4y8z7._.js} +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fooke1._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t6y79e._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x29muh._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a_6x37._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an_8pa._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ducpz2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xhyinn._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +1 -1
- package/.next/standalone/.next/server/chunks/_02twlca._.js +1 -1
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0sqqpzb._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_backup_route_actions_0zoj04p.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_csv_route_actions_0l6m7lg.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restore_route_actions_0-b05bv.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +4 -0
- package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +4 -0
- package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +4 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +174 -0
- package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_005abye.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b17r6v.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +7 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0doq0t-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0fd2rlw.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0is4qoc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0jnfp2e.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qd8pzm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qdeuyo._.js → [root-of-the-server]__1-012q1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_01o3i5y._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_06rgzfh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0gy11ar._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0hb-dhy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1k9qfsk._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +25 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +25 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_CompanyScores_1g0mytz.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +25 -13
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +11 -11
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +12 -13
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{lib_1c4ieuw._.js → lib_csv_1ttiu_h.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_separation_1305zsx.js +3 -0
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/03-_esd2x_yk6.js +1 -0
- package/.next/standalone/.next/static/chunks/03-demj2iy-0p.js +44 -0
- package/.next/standalone/.next/static/chunks/0_1r-3csplxnj.js +1 -0
- package/.next/standalone/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
- package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +21 -0
- package/.next/standalone/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
- package/.next/standalone/.next/static/chunks/0p3g5tfostsep.js +7 -0
- package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
- package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +34 -0
- package/.next/standalone/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
- package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
- package/.next/standalone/.next/static/chunks/1qbjermbf68xj.js +1 -0
- package/.next/standalone/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
- package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +57 -0
- package/.next/standalone/.next/static/chunks/260bomtr214m8.js +34 -0
- package/.next/standalone/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
- package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +34 -0
- package/.next/standalone/.next/static/chunks/2z1-q0_vunbql.js +2 -0
- package/.next/standalone/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
- package/.next/standalone/.next/static/chunks/34g73qhayuevu.js +1 -0
- package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +1 -0
- package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +1 -0
- package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +3 -0
- package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
- package/.next/standalone/.next/static/chunks/43pg2sursiypz.js +1 -0
- package/.next/standalone/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
- package/.next/standalone/CHANGELOG.md +143 -0
- package/.next/standalone/README.md +71 -49
- package/.next/standalone/SECURITY.md +17 -11
- package/.next/standalone/app/api/data/backup/route.js +25 -0
- package/.next/standalone/app/api/data/csv/route.js +74 -0
- package/.next/standalone/app/api/data/export/route.js +1 -1
- package/.next/standalone/app/api/data/restore/route.js +48 -0
- package/.next/standalone/app/api/data/reveal/route.js +19 -6
- package/.next/standalone/app/api/data/route.js +15 -4
- package/.next/standalone/app/api/ingest/route.js +1 -1
- package/.next/standalone/app/api/queue/route.js +1 -1
- package/.next/standalone/app/api/scraper/route.js +120 -32
- package/.next/standalone/app/api/social/crm/route.js +2 -1
- package/.next/standalone/app/components/AppHeader.js +20 -16
- package/.next/standalone/app/components/AppTabs.js +8 -8
- package/.next/standalone/app/components/AutoScanButton.js +2 -2
- package/.next/standalone/app/components/Avatar.js +1 -1
- package/.next/standalone/app/components/ChainView.js +184 -112
- package/.next/standalone/app/components/CirclePanel.js +16 -16
- package/.next/standalone/app/components/ClusterSpinner.js +64 -0
- package/.next/standalone/app/components/CompanyScores.js +20 -20
- package/.next/standalone/app/components/EmptyState.js +39 -18
- package/.next/standalone/app/components/FieldStep.js +15 -13
- package/.next/standalone/app/components/FilterPanel.js +17 -17
- package/.next/standalone/app/components/ForceGraph.js +49 -14
- package/.next/standalone/app/components/GalaxyLab.js +33 -33
- package/.next/standalone/app/components/GridView.js +18 -18
- package/.next/standalone/app/components/LegacyScoresCard.js +10 -11
- package/.next/standalone/app/components/LinkedInLimits.js +24 -19
- package/.next/standalone/app/components/LiquidGlass.js +99 -0
- package/.next/standalone/app/components/ListView.js +20 -20
- package/.next/standalone/app/components/MapControls.js +5 -5
- package/.next/standalone/app/components/MappingProgress.js +16 -16
- package/.next/standalone/app/components/NoteDetail.js +13 -13
- package/.next/standalone/app/components/OnboardingGate.js +4 -4
- package/.next/standalone/app/components/OutlinkQuest.js +25 -25
- package/.next/standalone/app/components/PathsAnalyzer.js +37 -36
- package/.next/standalone/app/components/PeopleMap.js +21 -21
- package/.next/standalone/app/components/RingsView.js +11 -11
- package/.next/standalone/app/components/ScanRadar.js +84 -39
- package/.next/standalone/app/components/ScanStatusBar.js +22 -18
- package/.next/standalone/app/components/ScanTrail.js +8 -5
- package/.next/standalone/app/components/SeparationView.js +168 -104
- package/.next/standalone/app/components/Sidebar.js +161 -161
- package/.next/standalone/app/components/StaleServerBanner.js +4 -4
- package/.next/standalone/app/components/TheirCircle.js +26 -26
- package/.next/standalone/app/components/ThemeDefs.js +42 -0
- package/.next/standalone/app/components/ThemeLoader.js +20 -1
- package/.next/standalone/app/components/UpdatePanel.js +22 -22
- package/.next/standalone/app/components/UserProvider.js +22 -15
- package/.next/standalone/app/components/settings/AppearanceSection.js +47 -24
- package/.next/standalone/app/components/settings/DataSection.js +276 -109
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +13 -13
- package/.next/standalone/app/components/settings/SectorPicker.js +12 -12
- package/.next/standalone/app/components/settings/SectorSection.js +13 -14
- package/.next/standalone/app/components/settings/TierSection.js +9 -9
- package/.next/standalone/app/components/settings/TitleSection.js +11 -11
- package/.next/standalone/app/components/settings/UsageSection.js +313 -0
- package/.next/standalone/app/components/ui.js +8 -8
- package/.next/standalone/app/globals.css +159 -2
- package/.next/standalone/app/import/page.js +94 -38
- package/.next/standalone/app/launch/page.js +5 -5
- package/.next/standalone/app/layout.js +6 -2
- package/.next/standalone/app/page.js +53 -37
- package/.next/standalone/app/paths/page.js +63 -63
- package/.next/standalone/app/profile/page.js +58 -58
- package/.next/standalone/app/queue/page.js +54 -54
- package/.next/standalone/app/scores/page.js +2 -2
- package/.next/standalone/app/settings/page.js +16 -13
- package/.next/standalone/app/setup/page.js +221 -125
- package/.next/standalone/app/social/Crm.js +63 -62
- package/.next/standalone/app/social/SocialHub.js +22 -22
- package/.next/standalone/app/social/Thread.js +7 -7
- package/.next/standalone/app/social/page.js +2 -2
- package/.next/standalone/lib/backups.js +458 -0
- package/.next/standalone/lib/chain-layout.js +32 -0
- package/.next/standalone/lib/csv-store.js +131 -0
- package/.next/standalone/lib/csv.js +137 -39
- package/.next/standalone/lib/data-folder.js +19 -3
- package/.next/standalone/lib/data-import.js +5 -2
- package/.next/standalone/lib/db-client.js +78 -20
- package/.next/standalone/lib/demo.js +5 -4
- package/.next/standalone/lib/galaxy-export.js +9 -2
- package/.next/standalone/lib/galaxy-lab.js +19 -5
- package/.next/standalone/lib/galaxy-layouts.js +43 -0
- package/.next/standalone/lib/gate.js +10 -4
- package/.next/standalone/lib/ingest.js +1 -1
- package/.next/standalone/lib/linkedin-limits.js +212 -11
- package/.next/standalone/lib/liquid-glass.js +59 -0
- package/.next/standalone/lib/notifications.js +13 -0
- package/.next/standalone/lib/scan-pace.js +11 -0
- package/.next/standalone/lib/scan-progress.js +20 -0
- package/.next/standalone/lib/scan-risk.js +44 -0
- package/.next/standalone/lib/scanner-setup.js +97 -15
- package/.next/standalone/lib/scraper-client.js +17 -3
- package/.next/standalone/lib/search-risk.js +7 -3
- package/.next/standalone/lib/settings-client.js +5 -3
- package/.next/standalone/lib/settings.js +10 -0
- package/.next/standalone/lib/social-store.js +3 -2
- package/.next/standalone/lib/synced-setting.js +46 -0
- package/.next/standalone/lib/theme-store.js +42 -10
- package/.next/standalone/lib/themes.js +111 -21
- package/.next/standalone/lib/usage.js +195 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +26 -2
- package/.next/static/chunks/03-_esd2x_yk6.js +1 -0
- package/.next/static/chunks/03-demj2iy-0p.js +44 -0
- package/.next/static/chunks/0_1r-3csplxnj.js +1 -0
- package/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
- package/.next/static/chunks/0e3g0sil79cus.js +21 -0
- package/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
- package/.next/static/chunks/0p3g5tfostsep.js +7 -0
- package/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
- package/.next/static/chunks/163b-20vwa06i.js +34 -0
- package/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
- package/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
- package/.next/static/chunks/1qbjermbf68xj.js +1 -0
- package/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
- package/.next/static/chunks/23_1z47al92nz.js +57 -0
- package/.next/static/chunks/260bomtr214m8.js +34 -0
- package/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
- package/.next/static/chunks/2xk2ra4piiuod.js +34 -0
- package/.next/static/chunks/2z1-q0_vunbql.js +2 -0
- package/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
- package/.next/static/chunks/34g73qhayuevu.js +1 -0
- package/.next/static/chunks/3b0-14cxqsprt.js +1 -0
- package/.next/static/chunks/3jarus1_38s2z.js +1 -0
- package/.next/static/chunks/3ssu341hav_sq.css +3 -0
- package/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
- package/.next/static/chunks/43pg2sursiypz.js +1 -0
- package/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
- package/CHANGELOG.md +143 -0
- package/README.md +71 -49
- package/SECURITY.md +17 -11
- package/lib/backups.js +458 -0
- package/lib/chain-layout.js +32 -0
- package/lib/csv-store.js +131 -0
- package/lib/csv.js +137 -39
- package/lib/data-folder.js +19 -3
- package/lib/data-import.js +5 -2
- package/lib/db-client.js +78 -20
- package/lib/demo.js +5 -4
- package/lib/galaxy-export.js +9 -2
- package/lib/galaxy-lab.js +19 -5
- package/lib/galaxy-layouts.js +43 -0
- package/lib/gate.js +10 -4
- package/lib/ingest.js +1 -1
- package/lib/linkedin-limits.js +212 -11
- package/lib/liquid-glass.js +59 -0
- package/lib/notifications.js +13 -0
- package/lib/scan-pace.js +11 -0
- package/lib/scan-progress.js +20 -0
- package/lib/scan-risk.js +44 -0
- package/lib/scanner-setup.js +97 -15
- package/lib/scraper-client.js +17 -3
- package/lib/search-risk.js +7 -3
- package/lib/settings-client.js +5 -3
- package/lib/settings.js +10 -0
- package/lib/social-store.js +3 -2
- package/lib/synced-setting.js +46 -0
- package/lib/theme-store.js +42 -10
- package/lib/themes.js +111 -21
- package/lib/usage.js +195 -0
- package/package.json +1 -1
- package/scripts/scrape.py +26 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +0 -174
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-658fq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +0 -3
- package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +0 -22
- package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
- package/.next/standalone/.next/static/chunks/0i02yxppzds1o.js +0 -1
- package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
- package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +0 -1
- package/.next/standalone/.next/static/chunks/0vo-l8hxneba6.css +0 -3
- package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
- package/.next/standalone/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
- package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +0 -34
- package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
- package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
- package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
- package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +0 -45
- package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +0 -1
- package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +0 -11
- package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
- package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +0 -1
- package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
- package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +0 -1
- package/.next/standalone/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
- package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +0 -11
- package/.next/static/chunks/02pq76u7y4v9-.js +0 -22
- package/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
- package/.next/static/chunks/0i02yxppzds1o.js +0 -1
- package/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
- package/.next/static/chunks/0oyslj62jkubd.js +0 -1
- package/.next/static/chunks/0vo-l8hxneba6.css +0 -3
- package/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
- package/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
- package/.next/static/chunks/172e10mi-tpo_.js +0 -34
- package/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
- package/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
- package/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
- package/.next/static/chunks/1m3017gp790pw.js +0 -45
- package/.next/static/chunks/1t20e8u0st7ep.js +0 -1
- package/.next/static/chunks/2-ch9rm27eril.js +0 -11
- package/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
- package/.next/static/chunks/3bp284gjf7dyq.js +0 -1
- package/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
- package/.next/static/chunks/3l4t8je6iguzc.js +0 -1
- package/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
- package/.next/static/chunks/42m0bqr8pbovz.js +0 -11
- /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
- /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
- /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
package/lib/ingest.js
CHANGED
|
@@ -89,7 +89,7 @@ export function refreshNotifications({ added = [], checked = 0, tierOf = () => n
|
|
|
89
89
|
if (!added.length) {
|
|
90
90
|
return [{
|
|
91
91
|
user_id, type: 'refresh_summary', title: 'Network up to date',
|
|
92
|
-
message: `Checked ${checked} connections
|
|
92
|
+
message: `Checked ${checked} connections: no new additions`, icon: '✓',
|
|
93
93
|
}];
|
|
94
94
|
}
|
|
95
95
|
const out = [{
|
package/lib/linkedin-limits.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
// reads them with the same rules, and writes only the two things a person
|
|
5
5
|
// changes here — the budget, and lifting a cooldown.
|
|
6
6
|
//
|
|
7
|
+
// Settings → LinkedIn usage reads the same record more closely (linkedinUsage):
|
|
8
|
+
// the last hour and the last 7 days, when the next search frees up, and the
|
|
9
|
+
// last time LinkedIn pushed back. lib/usage.js says what the numbers mean.
|
|
10
|
+
//
|
|
7
11
|
// The budget is searches (a day and a month) and profile views (a day). Profile
|
|
8
12
|
// views got an account restricted (TRAPS §16), so their cap has no "no limit",
|
|
9
13
|
// and the scanner also keeps any two a minute apart. Searches did too, at 373 in
|
|
@@ -11,10 +15,13 @@
|
|
|
11
15
|
//
|
|
12
16
|
// They belong to the LinkedIn account, not to a profile in the app. TRAPS §35.
|
|
13
17
|
|
|
14
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, lstatSync, readdirSync, openSync, readSync, closeSync,
|
|
20
|
+
} from 'node:fs';
|
|
15
21
|
import path from 'node:path';
|
|
16
22
|
import { durable } from './durable.js';
|
|
17
23
|
import { PACES, DEFAULT_PACE } from './scan-pace.js';
|
|
24
|
+
import { countAfter } from './usage.js';
|
|
18
25
|
|
|
19
26
|
export const DEFAULT_LIMITS = { daily: 50, monthly: 250, profiles: 50, pace: DEFAULT_PACE };
|
|
20
27
|
export const DAILY_CHOICES = [25, 50, 100, 200, 500];
|
|
@@ -94,6 +101,20 @@ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
|
|
|
94
101
|
return next;
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The scanner's record as two lists of times (epoch seconds), or null when it
|
|
106
|
+
* can't be read. No record at all is nothing done yet.
|
|
107
|
+
*/
|
|
108
|
+
function readActivity(dir) {
|
|
109
|
+
const file = path.join(dir, 'linkedin-activity.json');
|
|
110
|
+
const data = existsSync(file) ? readJson(file) : { searches: [], profiles: [] };
|
|
111
|
+
if (!data || typeof data !== 'object') return null;
|
|
112
|
+
return {
|
|
113
|
+
searches: (Array.isArray(data.searches) ? data.searches : []).map(Number),
|
|
114
|
+
profiles: (Array.isArray(data.profiles) ? data.profiles : []).map(Number),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
97
118
|
/**
|
|
98
119
|
* Searches in the last 24 hours and since LinkedIn's month began, and profile
|
|
99
120
|
* views in the last 24 hours. An unreadable record reads as the day used up,
|
|
@@ -101,11 +122,9 @@ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
|
|
|
101
122
|
* would say nothing had been searched.
|
|
102
123
|
*/
|
|
103
124
|
export function usage(dir, now = Date.now()) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const s = (Array.isArray(data.searches) ? data.searches : []).map(Number);
|
|
108
|
-
const p = (Array.isArray(data.profiles) ? data.profiles : []).map(Number);
|
|
125
|
+
const rec = readActivity(dir);
|
|
126
|
+
if (!rec) return { searchesToday: Infinity, searchesMonth: 0, profilesToday: Infinity, unreadable: true };
|
|
127
|
+
const { searches: s, profiles: p } = rec;
|
|
109
128
|
const dayAgo = now / 1000 - DAY;
|
|
110
129
|
const month0 = monthStartPacific(now) / 1000;
|
|
111
130
|
return {
|
|
@@ -115,17 +134,42 @@ export function usage(dir, now = Date.now()) {
|
|
|
115
134
|
};
|
|
116
135
|
}
|
|
117
136
|
|
|
118
|
-
/** The active cooldown { until (ms), reason, setAt }, or null. */
|
|
137
|
+
/** The active cooldown { until (ms), reason, setAt }, or null. A lifted one (liftCooldown) is none. */
|
|
119
138
|
export function readCooldown(dir, now = Date.now()) {
|
|
120
139
|
const raw = readJson(path.join(dir, 'linkedin-cooldown.json'));
|
|
121
140
|
const until = Number(raw?.until) * 1000;
|
|
122
|
-
if (!raw || !Number.isFinite(until) || until <= now) return null;
|
|
141
|
+
if (!raw || raw.lifted_at !== undefined || !Number.isFinite(until) || until <= now) return null;
|
|
123
142
|
return { until, reason: String(raw.reason || 'LinkedIn pushed back'), setAt: Number(raw.set_at) * 1000 || null };
|
|
124
143
|
}
|
|
125
144
|
|
|
126
|
-
/**
|
|
127
|
-
|
|
128
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Lift it: the person says LinkedIn works for them again. The record is kept,
|
|
147
|
+
* marked lifted, so Settings → LinkedIn usage can still say when LinkedIn last
|
|
148
|
+
* pushed back; deleting it forgot that. Its `until` becomes the moment it was
|
|
149
|
+
* lifted and the old end moves to `was_until`, so everything that reads the
|
|
150
|
+
* file sees a pause that has simply ended, with no change of its own: here
|
|
151
|
+
* (readCooldown, the import's merge) and in the scanner (read_cooldown and
|
|
152
|
+
* set_cooldown in scripts/scrape.py ask only whether `until` is still ahead),
|
|
153
|
+
* so the scanner's pacing is exactly what it was. A pause already over is
|
|
154
|
+
* left alone; a record that can't be read is removed, as before.
|
|
155
|
+
*/
|
|
156
|
+
export function liftCooldown(dir, now = Date.now()) {
|
|
157
|
+
const file = path.join(dir, COOLDOWN);
|
|
158
|
+
const raw = readJson(file);
|
|
159
|
+
const until = Number(raw?.until);
|
|
160
|
+
if (!isObject(raw) || !Number.isFinite(until)) {
|
|
161
|
+
try { unlinkSync(file); } catch { /* none to lift */ }
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const at = now / 1000;
|
|
165
|
+
if (until <= at) return;
|
|
166
|
+
writeJsonAtomic(file, {
|
|
167
|
+
until: at,
|
|
168
|
+
reason: typeof raw.reason === 'string' ? raw.reason : 'LinkedIn pushed back',
|
|
169
|
+
set_at: isTime(raw.set_at) ? raw.set_at : null,
|
|
170
|
+
lifted_at: at,
|
|
171
|
+
was_until: until,
|
|
172
|
+
});
|
|
129
173
|
}
|
|
130
174
|
|
|
131
175
|
/** Everything the Scan page shows, in one object. */
|
|
@@ -144,6 +188,155 @@ export function linkedinState(dir, now = Date.now()) {
|
|
|
144
188
|
};
|
|
145
189
|
}
|
|
146
190
|
|
|
191
|
+
// ── Settings → LinkedIn usage ────────────────────────────────────────────────
|
|
192
|
+
// The same record, read more closely. Windows count a time strictly inside
|
|
193
|
+
// them (t > now - window), as usage() and the scanner's linkedin_usage do, so a
|
|
194
|
+
// search exactly 24 hours old has stopped counting.
|
|
195
|
+
|
|
196
|
+
/** How many of `times` (epoch s) fall in the last `seconds` before `now` (ms). */
|
|
197
|
+
export function countWithin(times, now, seconds) {
|
|
198
|
+
const from = now / 1000 - seconds;
|
|
199
|
+
return times.filter((t) => t > from).length;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* When the count in the window next drops under `cap`, in ms: the moment a
|
|
204
|
+
* search (or profile view) is free again. null when one is free now, or with
|
|
205
|
+
* no cap. The scanner says the same for profile views (scrape.py
|
|
206
|
+
* budget_message: the one that stops counting soonest frees the next).
|
|
207
|
+
*/
|
|
208
|
+
export function freesAt(times, now, cap, seconds = DAY) {
|
|
209
|
+
if (!cap) return null;
|
|
210
|
+
const live = times.filter((t) => t > now / 1000 - seconds).sort((a, b) => a - b);
|
|
211
|
+
if (live.length < cap) return null;
|
|
212
|
+
return (live[live.length - cap] + seconds) * 1000;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** When the window holds nothing at all again (the newest time turns `seconds` old), in ms; null if it's empty. */
|
|
216
|
+
export function clearAt(times, now, seconds = DAY) {
|
|
217
|
+
const live = times.filter((t) => t > now / 1000 - seconds);
|
|
218
|
+
return live.length ? (Math.max(...live) + seconds) * 1000 : null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// The scanner keeps what the page showed when LinkedIn pushed back as
|
|
222
|
+
// pushback/<local date>_<time>.txt: "reason: …", then "url: …", then the page's
|
|
223
|
+
// text (scrape.py _keep_pushback_evidence). Only the name's time and the first
|
|
224
|
+
// line are read here: the page's address and its text are LinkedIn's page about
|
|
225
|
+
// this account and stay in the file.
|
|
226
|
+
const PUSHBACK_FILE = /^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})\.txt$/;
|
|
227
|
+
const FIRST_LINE_BYTES = 256;
|
|
228
|
+
|
|
229
|
+
/** The reason on a pushback file's first line, or null. Never more of the file. */
|
|
230
|
+
function pushbackReason(file) {
|
|
231
|
+
let fd;
|
|
232
|
+
try {
|
|
233
|
+
fd = openSync(file, 'r');
|
|
234
|
+
const buf = Buffer.alloc(FIRST_LINE_BYTES);
|
|
235
|
+
const n = readSync(fd, buf, 0, FIRST_LINE_BYTES, 0);
|
|
236
|
+
const line = buf.subarray(0, n).toString('utf8').split(/\r?\n/)[0];
|
|
237
|
+
const m = line.match(/^reason: (.+)$/);
|
|
238
|
+
const reason = m && m[1].replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 120);
|
|
239
|
+
// The scanner's reasons are its own short phrases; anything shaped like an address isn't one.
|
|
240
|
+
return reason && !/:\/\/|www\.|linkedin\.com|\/in\//i.test(reason) ? reason : null;
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
} finally {
|
|
244
|
+
if (fd !== undefined) closeSync(fd);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** The newest pushback file: { at (ms, from its name), reason }, or null. */
|
|
249
|
+
export function newestPushback(dir) {
|
|
250
|
+
let names;
|
|
251
|
+
try {
|
|
252
|
+
// Plain files only: a link or a folder by that name is not the scanner's.
|
|
253
|
+
names = readdirSync(path.join(dir, 'pushback'), { withFileTypes: true })
|
|
254
|
+
.filter((e) => e.isFile() && PUSHBACK_FILE.test(e.name)).map((e) => e.name);
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
// Newest first by name, which is by time; a name that isn't a real time is passed over.
|
|
259
|
+
for (const name of names.sort().reverse()) {
|
|
260
|
+
const [, y, mo, d, h, mi, s] = name.match(PUSHBACK_FILE).map(Number);
|
|
261
|
+
// The scanner names it by this computer's clock (datetime.now()), as this reads it.
|
|
262
|
+
const when = new Date(y, mo - 1, d, h, mi, s);
|
|
263
|
+
if (when.getMonth() !== mo - 1 || when.getDate() !== d || h > 23 || mi > 59 || s > 59) continue;
|
|
264
|
+
return { at: when.getTime(), reason: pushbackReason(path.join(dir, 'pushback', name)) };
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// One pushback usually leaves both: the page kept, then the pause set, moments
|
|
270
|
+
// (at most the rest of that person's read) apart.
|
|
271
|
+
const SAME_EVENT_MS = 10 * 60 * 1000;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The last time LinkedIn pushed back, from the cooldown file (active, ended or
|
|
275
|
+
* lifted) and the newest pushback file: { at (ms or null), reason, pausedUntil,
|
|
276
|
+
* liftedAt, active }, or null when there's no sign of one. Times and the
|
|
277
|
+
* reason only.
|
|
278
|
+
*/
|
|
279
|
+
export function lastPushback(dir, now = Date.now()) {
|
|
280
|
+
const file = newestPushback(dir);
|
|
281
|
+
const raw = readJson(path.join(dir, COOLDOWN));
|
|
282
|
+
const until = Number(raw?.until);
|
|
283
|
+
const pause = isObject(raw) && Number.isFinite(until) ? {
|
|
284
|
+
at: isTime(raw.set_at) ? raw.set_at * 1000 : null,
|
|
285
|
+
reason: typeof raw.reason === 'string' && raw.reason.trim() ? raw.reason.trim().slice(0, 160) : 'LinkedIn pushed back',
|
|
286
|
+
pausedUntil: (isTime(raw.was_until) ? raw.was_until : until) * 1000,
|
|
287
|
+
liftedAt: isTime(raw.lifted_at) ? raw.lifted_at * 1000 : null,
|
|
288
|
+
} : null;
|
|
289
|
+
const fromFile = file && {
|
|
290
|
+
at: file.at,
|
|
291
|
+
reason: file.reason ? `LinkedIn pushed back: ${file.reason}` : 'LinkedIn pushed back',
|
|
292
|
+
pausedUntil: null,
|
|
293
|
+
liftedAt: null,
|
|
294
|
+
};
|
|
295
|
+
let last;
|
|
296
|
+
if (pause && fromFile && pause.at != null && Math.abs(pause.at - fromFile.at) <= SAME_EVENT_MS) {
|
|
297
|
+
last = { ...pause, at: Math.min(pause.at, fromFile.at) };
|
|
298
|
+
} else if (pause && fromFile) {
|
|
299
|
+
last = (pause.at ?? -Infinity) >= fromFile.at ? pause : fromFile;
|
|
300
|
+
} else {
|
|
301
|
+
last = pause || fromFile;
|
|
302
|
+
}
|
|
303
|
+
if (!last) return null;
|
|
304
|
+
return { ...last, active: !last.liftedAt && last.pausedUntil != null && last.pausedUntil > now };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Everything Settings → LinkedIn usage shows (GET /api/scraper?usage): the
|
|
309
|
+
* Scan page's state, plus
|
|
310
|
+
* searchesLastHour, searchesWeek counted like searchesToday
|
|
311
|
+
* dayFreesAt when the daily budget has a search free again (null: one is free now)
|
|
312
|
+
* dayClearAt when the last 24 hours hold no searches at all
|
|
313
|
+
* profilesFreeAt when a profile view is free again
|
|
314
|
+
* lastPushback lastPushback() above
|
|
315
|
+
* searchesSincePushback searches made after it (lib/usage.js countAfter)
|
|
316
|
+
* An unreadable record gives nulls, never zeros: it isn't "nothing searched".
|
|
317
|
+
*/
|
|
318
|
+
export function linkedinUsage(dir, now = Date.now()) {
|
|
319
|
+
const state = linkedinState(dir, now);
|
|
320
|
+
const rec = readActivity(dir);
|
|
321
|
+
const pushback = lastPushback(dir, now);
|
|
322
|
+
const known = (fn) => (rec ? fn(rec) : null);
|
|
323
|
+
return {
|
|
324
|
+
...state,
|
|
325
|
+
now,
|
|
326
|
+
searchesToday: rec ? state.searchesToday : null,
|
|
327
|
+
profilesToday: rec ? state.profilesToday : null,
|
|
328
|
+
searchesMonth: rec ? state.searchesMonth : null,
|
|
329
|
+
leftMonth: rec ? state.leftMonth : null,
|
|
330
|
+
searchesLastHour: known((r) => countWithin(r.searches, now, 3600)),
|
|
331
|
+
searchesWeek: known((r) => countWithin(r.searches, now, 7 * DAY)),
|
|
332
|
+
dayFreesAt: known((r) => freesAt(r.searches, now, state.limits.daily)),
|
|
333
|
+
dayClearAt: known((r) => clearAt(r.searches, now)),
|
|
334
|
+
profilesFreeAt: known((r) => freesAt(r.profiles, now, state.limits.profiles)),
|
|
335
|
+
lastPushback: pushback,
|
|
336
|
+
searchesSincePushback: known((r) => countAfter(r.searches, pushback?.at)),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
147
340
|
// ── another computer's budget (Settings → Your data → Import) ───────────────
|
|
148
341
|
// An import carries these three files from the other computer. They describe
|
|
149
342
|
// the same LinkedIn account, so they are merged with the ones here, never put
|
|
@@ -175,6 +368,10 @@ export function budgetFileProblem(name, data) {
|
|
|
175
368
|
if (!isTime(data.until)) return 'its pause has no end time';
|
|
176
369
|
if (data.reason !== undefined && typeof data.reason !== 'string') return 'its pause has a reason that is not text';
|
|
177
370
|
if (data.set_at !== undefined && !isTime(data.set_at)) return 'its pause has a start time that is not a time';
|
|
371
|
+
// A pause lifted by hand (liftCooldown) says when, and when it would have ended.
|
|
372
|
+
for (const key of ['lifted_at', 'was_until']) {
|
|
373
|
+
if (data[key] !== undefined && !isTime(data[key])) return 'its lifted pause has a time that is not a time';
|
|
374
|
+
}
|
|
178
375
|
} else if (name === LIMITS) {
|
|
179
376
|
if (data.daily !== undefined && !DAILY_CHOICES.includes(data.daily)) {
|
|
180
377
|
return `its daily limit (${String(data.daily).slice(0, 20)}) is not one Six Degrees offers`;
|
|
@@ -252,11 +449,15 @@ export function mergeBudgetFiles({ dir, from }) {
|
|
|
252
449
|
if (cooldown) {
|
|
253
450
|
const mine = readJson(here(COOLDOWN));
|
|
254
451
|
const mineEnds = Number.isFinite(Number(mine?.until)) ? Number(mine.until) : -Infinity;
|
|
452
|
+
// A pause lifted here ends when it was lifted (liftCooldown), so one still
|
|
453
|
+
// on in the copy comes back, as it did when lifting deleted the file.
|
|
255
454
|
if (cooldown.until > mineEnds) {
|
|
256
455
|
durable.writeJsonDurably(here(COOLDOWN), {
|
|
257
456
|
until: cooldown.until,
|
|
258
457
|
reason: cooldown.reason ?? 'LinkedIn pushed back',
|
|
259
458
|
set_at: cooldown.set_at ?? null,
|
|
459
|
+
// Lifted in the copy: still lifted here, and still the last pushback on record.
|
|
460
|
+
...(cooldown.lifted_at !== undefined && { lifted_at: cooldown.lifted_at, was_until: cooldown.was_until ?? cooldown.until }),
|
|
260
461
|
});
|
|
261
462
|
}
|
|
262
463
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Liquid Glass's lensing (Apple, WWDC25 "Meet Liquid Glass"; HIG → Materials):
|
|
2
|
+
// the edge of a pane of glass bends what's behind it, where frosted glass only
|
|
3
|
+
// blurs it. Done the way the good web versions do it in Chromium: a
|
|
4
|
+
// displacement map, drawn here for a panel's exact size, used by an SVG
|
|
5
|
+
// feDisplacementMap that the panel's backdrop-filter runs
|
|
6
|
+
// (app/components/LiquidGlass.js).
|
|
7
|
+
//
|
|
8
|
+
// The map's red channel shifts a pixel across, green up and down; 128 is no
|
|
9
|
+
// shift. Only a band along the edge (the bezel) moves, and it always pulls
|
|
10
|
+
// inward, since a backdrop is clipped to the panel and anything pulled from
|
|
11
|
+
// outside it would come back empty.
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* How far, and which way, each pixel of a rounded rectangle w×h (corner r)
|
|
15
|
+
* bends: [dx, dy] in -1…1 for (x, y), strongest at the edge and fading to
|
|
16
|
+
* nothing `bezel` pixels in.
|
|
17
|
+
*/
|
|
18
|
+
export function lensAt(x, y, w, h, r, bezel) {
|
|
19
|
+
const px = x + 0.5 - w / 2;
|
|
20
|
+
const py = y + 0.5 - h / 2;
|
|
21
|
+
const qx = Math.abs(px) - (w / 2 - r);
|
|
22
|
+
const qy = Math.abs(py) - (h / 2 - r);
|
|
23
|
+
const ox = Math.max(qx, 0);
|
|
24
|
+
const oy = Math.max(qy, 0);
|
|
25
|
+
const o = Math.hypot(ox, oy);
|
|
26
|
+
const dist = r - (o + Math.min(Math.max(qx, qy), 0)); // pixels in from the edge
|
|
27
|
+
let nx = 0;
|
|
28
|
+
let ny = 0;
|
|
29
|
+
if (o > 0) { nx = ox / o; ny = oy / o; } else if (qx > qy) nx = 1; else ny = 1;
|
|
30
|
+
nx *= -Math.sign(px) || 0;
|
|
31
|
+
ny *= -Math.sign(py) || 0;
|
|
32
|
+
const t = Math.max(0, 1 - dist / bezel);
|
|
33
|
+
const m = t * t;
|
|
34
|
+
return [nx * m, ny * m];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The displacement map for a w×h panel, as a PNG data URL (needs a canvas: the browser only). */
|
|
38
|
+
export function lensMap(w, h, r, bezel) {
|
|
39
|
+
const c = document.createElement('canvas');
|
|
40
|
+
c.width = w;
|
|
41
|
+
c.height = h;
|
|
42
|
+
const ctx = c.getContext('2d');
|
|
43
|
+
const img = ctx.createImageData(w, h);
|
|
44
|
+
const d = img.data;
|
|
45
|
+
for (let y = 0; y < h; y++) {
|
|
46
|
+
for (let x = 0; x < w; x++) {
|
|
47
|
+
const i = (y * w + x) * 4;
|
|
48
|
+
// Most of a big panel is its middle, which never moves.
|
|
49
|
+
const nearEdge = x < bezel + r || y < bezel + r || x >= w - bezel - r || y >= h - bezel - r;
|
|
50
|
+
const [dx, dy] = nearEdge ? lensAt(x, y, w, h, r, bezel) : [0, 0];
|
|
51
|
+
d[i] = 128 + 127 * dx;
|
|
52
|
+
d[i + 1] = 128 + 127 * dy;
|
|
53
|
+
d[i + 2] = 128;
|
|
54
|
+
d[i + 3] = 255;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
ctx.putImageData(img, 0, 0);
|
|
58
|
+
return c.toDataURL();
|
|
59
|
+
}
|
package/lib/notifications.js
CHANGED
|
@@ -94,6 +94,19 @@ const NAME_IN_TITLE = [
|
|
|
94
94
|
* `via` is who you met them through: the notification's own, else the person's
|
|
95
95
|
* recorded introducer. `people` are everyone it names (a top-company list).
|
|
96
96
|
*/
|
|
97
|
+
/**
|
|
98
|
+
* Which circle a notification is about, as a connection's id (a string), or
|
|
99
|
+
* null: whoever someone came through for "added you back" and "top companies",
|
|
100
|
+
* and whose circle it was for "Scan done". Read from what the note saved, so it
|
|
101
|
+
* needs nothing loaded.
|
|
102
|
+
*/
|
|
103
|
+
export function noteCircle(note) {
|
|
104
|
+
const data = typeof note?.data === 'string' ? safeJson(note.data) : note?.data || {};
|
|
105
|
+
if (data.viaId != null) return String(data.viaId);
|
|
106
|
+
if (data.action && data.personId != null) return String(data.personId);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
export function noteSubject(note, connections = [], degree2 = []) {
|
|
98
111
|
const all = [...(connections || []), ...(degree2 || [])];
|
|
99
112
|
const byId = new Map(all.map((r) => [r.id, r]));
|
package/lib/scan-pace.js
CHANGED
|
@@ -29,6 +29,17 @@ export function paceSeconds(name, searches) {
|
|
|
29
29
|
return n * (WORK_SECONDS + p.pagePause) + Math.floor(n / EVERY) * p.chunkCooldown;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* About how long a circle scan takes to show its first people on the map. The
|
|
34
|
+
* scanner saves after its first ten pages (scrape.py SAVE_EVERY_PAGES), so it's
|
|
35
|
+
* those ten at this speed; the longer rest counted after them stands in for
|
|
36
|
+
* opening Chrome and the person's profile first. About 5 minutes at Fast: the
|
|
37
|
+
* Scan page's honest "first circle" (the rest of a network takes days).
|
|
38
|
+
*/
|
|
39
|
+
export function firstCircleSeconds(name) {
|
|
40
|
+
return paceSeconds(name, EVERY);
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
/** About how many searches an hour a speed makes. */
|
|
33
44
|
export function searchesPerHour(name) {
|
|
34
45
|
const p = paceOf(name);
|
package/lib/scan-progress.js
CHANGED
|
@@ -70,6 +70,26 @@ export function scanProgress(log, action) {
|
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// "[3/10] Ada Park (A-tier, score 6.2)", with ", carrying on from page 11"
|
|
74
|
+
// after it when a read goes on: the name is everything before the last
|
|
75
|
+
// "(…-tier, score …)".
|
|
76
|
+
const BRIDGE_PERSON = /^\[\d+\/\d+\] (.+) \([^()]*-tier, score [^()]*\)/;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whose circle a batch (Map 2nd degree, every paused list) is reading now,
|
|
80
|
+
* by name, from the line it printed as that person started; null for any
|
|
81
|
+
* other job, or before the first. The Scan page's "Watch it fill in" link
|
|
82
|
+
* (app/api/scraper/route.js turns the name into one of your connections).
|
|
83
|
+
*/
|
|
84
|
+
export function mappingNow(log, action) {
|
|
85
|
+
if (!Array.isArray(log) || !action || !(action.startsWith('auto-bridge') || action === 'resume-all')) return null;
|
|
86
|
+
for (let i = log.length - 1; i >= 0; i--) {
|
|
87
|
+
const m = String(log[i]).match(BRIDGE_PERSON);
|
|
88
|
+
if (m) return m[1];
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
function walk(done, total) {
|
|
74
94
|
if (!total) return null;
|
|
75
95
|
return { done: Math.min(done, total), total, kind: 'walk' };
|
package/lib/scan-risk.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Saying yes to the scanner's risk, once, before it first touches LinkedIn.
|
|
2
|
+
//
|
|
3
|
+
// The Scan page used to carry the risk as a grey line at the bottom. Now the
|
|
4
|
+
// first scan waits for an explicit "I understand" to three plain points, kept
|
|
5
|
+
// as a setting (app_meta, lib/settings.js) so it travels with a copy of the
|
|
6
|
+
// data. The server checks it too, so no button anywhere (the header's Check for
|
|
7
|
+
// new, a Scan button in a panel) can start a first scan without it.
|
|
8
|
+
//
|
|
9
|
+
// Someone who scanned before this existed has seen the risk and chosen it: the
|
|
10
|
+
// scanner's own Chrome profile in the data folder is the sign, since only the
|
|
11
|
+
// scanner makes one, and it never travels in a copy. They aren't asked again.
|
|
12
|
+
//
|
|
13
|
+
// No Node imports: the Scan page reads the points from here too.
|
|
14
|
+
|
|
15
|
+
export const RISK_POINTS = [
|
|
16
|
+
'It runs your own LinkedIn account automatically, in a Chrome window on this computer.',
|
|
17
|
+
'LinkedIn’s User Agreement doesn’t allow automated tools, and LinkedIn may restrict accounts that use them. The safe limits, 50 searches a day and 250 a month, stay on unless you change them.',
|
|
18
|
+
'Nothing leaves your computer. There is no account with us and nothing is uploaded.',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const RISK_REFUSAL = 'Before the first scan, open the Scan page and confirm you understand what scanning risks.';
|
|
22
|
+
|
|
23
|
+
// The scanner's actions that never open LinkedIn: installing its Python and
|
|
24
|
+
// packages, and saving photos already linked from it (no browser, no search).
|
|
25
|
+
const OFF_LINKEDIN = new Set(['install', 'setup', 'photos']);
|
|
26
|
+
|
|
27
|
+
export const touchesLinkedIn = (action) => !OFF_LINKEDIN.has(String(action));
|
|
28
|
+
|
|
29
|
+
/** When the risk was accepted (ISO time), or null; set from the Scan page. */
|
|
30
|
+
export const SCAN_RISK_SETTING = {
|
|
31
|
+
default: null,
|
|
32
|
+
parse(value) {
|
|
33
|
+
if (value === null) return null;
|
|
34
|
+
if (typeof value !== 'string' || Number.isNaN(Date.parse(value))) {
|
|
35
|
+
throw new Error('When the scanning risk was accepted is a date and time.');
|
|
36
|
+
}
|
|
37
|
+
return new Date(value).toISOString();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** @param {{ acceptedAt?: string | null, scannedBefore?: boolean }} state */
|
|
42
|
+
export function riskAccepted({ acceptedAt = null, scannedBefore = false } = {}) {
|
|
43
|
+
return Boolean(acceptedAt) || scannedBefore;
|
|
44
|
+
}
|
package/lib/scanner-setup.js
CHANGED
|
@@ -1,12 +1,71 @@
|
|
|
1
1
|
// What the Scan page's first step says and offers, from the checks GET
|
|
2
|
-
// /api/scraper reports (app/api/scraper/route.js, lib/scanner-python.js),
|
|
3
|
-
// whether it asks for your field
|
|
2
|
+
// /api/scraper reports (app/api/scraper/route.js, lib/scanner-python.js),
|
|
3
|
+
// whether it asks for your field once your connections are in (askForField),
|
|
4
|
+
// and two answers the server gives that step: is Google Chrome here
|
|
5
|
+
// (chromeInstalled), and are you signed in to LinkedIn (signedInFrom).
|
|
4
6
|
// Plain data in, plain data out: the page draws it, and
|
|
5
7
|
// tests/scanner-setup.test.mjs checks every state without a browser. No Node
|
|
6
8
|
// imports here; the page runs in the browser.
|
|
7
9
|
|
|
8
10
|
const MB = 1024 * 1024;
|
|
9
11
|
|
|
12
|
+
// The scanner drives Google Chrome (scripts/scrape.py, channel "chrome"), so a
|
|
13
|
+
// computer without it can set up the scanner's Python and still never scan.
|
|
14
|
+
// It used to be a red box above the steps while step 1 said Ready; now step 1
|
|
15
|
+
// isn't done without it, and the server refuses anything that opens LinkedIn
|
|
16
|
+
// with the same words (app/api/scraper/route.js).
|
|
17
|
+
export const CHROME_DOWNLOAD = 'https://www.google.com/chrome/';
|
|
18
|
+
export const CHROME_MISSING = 'The scanner works in Google Chrome, and Chrome isn’t on this computer.';
|
|
19
|
+
export const CHROME_BUTTON = 'Install Google Chrome, then come back';
|
|
20
|
+
export const CHROME_REFUSAL = `${CHROME_MISSING} ${CHROME_BUTTON}.`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Is Google Chrome where Playwright's "chrome" channel looks for it (Chromium
|
|
24
|
+
* doesn't count)? `exists` is the file check (existsSync on the server), so
|
|
25
|
+
* this runs in a test with no Chrome at all.
|
|
26
|
+
*
|
|
27
|
+
* SIX_DEGREES_TEST_CHROME=found or =missing answers for the computer, for
|
|
28
|
+
* tests and for checking the Scan page's states by eye: the route's tests then
|
|
29
|
+
* don't depend on whether the machine running them has Chrome. Nothing in the
|
|
30
|
+
* app sets it; any other value is ignored.
|
|
31
|
+
*/
|
|
32
|
+
export function chromeInstalled({ platform, env = {}, exists }) {
|
|
33
|
+
const forced = env.SIX_DEGREES_TEST_CHROME;
|
|
34
|
+
if (forced === 'found') return true;
|
|
35
|
+
if (forced === 'missing') return false;
|
|
36
|
+
if (platform === 'darwin') return exists('/Applications/Google Chrome.app');
|
|
37
|
+
if (platform === 'linux') return exists('/opt/google/chrome/chrome');
|
|
38
|
+
// On Windows, for this user, then for everyone.
|
|
39
|
+
if (platform === 'win32') {
|
|
40
|
+
return [env.LOCALAPPDATA, env.PROGRAMFILES, env['PROGRAMFILES(X86)']]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.some((root) => exists(`${String(root).replace(/[\\/]+$/, '')}\\Google\\Chrome\\Application\\chrome.exe`));
|
|
43
|
+
}
|
|
44
|
+
return true; // elsewhere Playwright resolves the channel itself
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The scanner's note that LinkedIn's session was confirmed (scripts/scrape.py
|
|
48
|
+
// SIGNED_IN_FILE): { signedIn, at }, written when the li_at cookie is seen past
|
|
49
|
+
// any sign-in wall, and set to false while it waits for you to sign in.
|
|
50
|
+
export const SIGNED_IN_FILE = 'signed-in.json';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Step 2's "Signed in". It used to be "Chrome's cookie file exists", which is
|
|
54
|
+
* true the moment the scanner's window first opens, so opening LinkedIn and
|
|
55
|
+
* closing the window without signing in ticked the step. Now the scanner's own
|
|
56
|
+
* note says it (`note`, the parsed SIGNED_IN_FILE, or null when there is none
|
|
57
|
+
* or it can't be read), and only while its Chrome profile is still there
|
|
58
|
+
* (`profile`): deleting that signs you out.
|
|
59
|
+
*
|
|
60
|
+
* A data folder from before the note existed has none, and there the old
|
|
61
|
+
* check stands (`cookies`), so nobody already signed in is told they're signed
|
|
62
|
+
* out; the next scan writes the note.
|
|
63
|
+
*/
|
|
64
|
+
export function signedInFrom({ note, profile, cookies }) {
|
|
65
|
+
if (note && typeof note === 'object' && typeof note.signedIn === 'boolean') return note.signedIn && Boolean(profile);
|
|
66
|
+
return Boolean(cookies);
|
|
67
|
+
}
|
|
68
|
+
|
|
10
69
|
/** "Python 3.9.6 is too old…": why a Python this computer has won't do, or ''. */
|
|
11
70
|
function whyNotThisPython(found) {
|
|
12
71
|
if (!found?.version) return 'This computer has no Python the scanner can use.';
|
|
@@ -40,10 +99,11 @@ function ownPythonNote(own, { done, button }) {
|
|
|
40
99
|
}
|
|
41
100
|
|
|
42
101
|
/**
|
|
43
|
-
* Step 1, "Set up the scanner": { done, text, button, note }, where
|
|
44
|
-
* { action: 'install'|'setup', label } or null (nothing to press),
|
|
45
|
-
* a line to show above the step when the Python inside the app didn't
|
|
46
|
-
* (ownPythonNote), or null
|
|
102
|
+
* Step 1, "Set up the scanner": { done, text, button, note, chrome }, where
|
|
103
|
+
* button is { action: 'install'|'setup', label } or null (nothing to press),
|
|
104
|
+
* note is a line to show above the step when the Python inside the app didn't
|
|
105
|
+
* work (ownPythonNote), or null, and chrome is { label, href } when Google
|
|
106
|
+
* Chrome isn't on this computer (the way to get it), or null.
|
|
47
107
|
*
|
|
48
108
|
* ready the app's own Python (nothing to install), one named by
|
|
49
109
|
* SIX_DEGREES_PYTHON, or the scanner's environment / a Python
|
|
@@ -53,10 +113,24 @@ function ownPythonNote(own, { done, button }) {
|
|
|
53
113
|
* setup none, but a standalone Python for this computer is pinned:
|
|
54
114
|
* "Set up the scanner" downloads it, then installs
|
|
55
115
|
* neither no Python and nothing to download for this computer
|
|
116
|
+
*
|
|
117
|
+
* Any of them without Chrome isn't done: the Python's part is said first if
|
|
118
|
+
* there is one, then Chrome's. Only a definite "no Chrome" counts; a status
|
|
119
|
+
* that couldn't say (none yet, or an older server's) leaves it out.
|
|
56
120
|
*/
|
|
57
121
|
export function setupStep(status) {
|
|
58
122
|
const step = stepOnly(status);
|
|
59
|
-
|
|
123
|
+
const note = ownPythonNote(status?.checks?.ownPython, step);
|
|
124
|
+
if (status?.checks?.chrome !== false) return { ...step, note, chrome: null };
|
|
125
|
+
return {
|
|
126
|
+
...step,
|
|
127
|
+
done: false,
|
|
128
|
+
// "Ready" or "Installed." would read as finished: with the Python done,
|
|
129
|
+
// Chrome is all the step says.
|
|
130
|
+
text: step.done ? CHROME_MISSING : `${step.text} ${CHROME_MISSING}`,
|
|
131
|
+
note,
|
|
132
|
+
chrome: { label: CHROME_BUTTON, href: CHROME_DOWNLOAD },
|
|
133
|
+
};
|
|
60
134
|
}
|
|
61
135
|
|
|
62
136
|
function stepOnly(status) {
|
|
@@ -105,19 +179,27 @@ function stepOnly(status) {
|
|
|
105
179
|
}
|
|
106
180
|
|
|
107
181
|
/**
|
|
108
|
-
* Whether the Scan page asks for your field
|
|
109
|
-
* once,
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
182
|
+
* Whether the Scan page asks for your field (app/components/FieldStep.js):
|
|
183
|
+
* once, when your own connections are in (step 3) and before who they know is
|
|
184
|
+
* mapped, under "Your galaxy is ready". It used to stand in front of step 1,
|
|
185
|
+
* one more thing between someone new and their first scan; asked here, it
|
|
186
|
+
* rescores people they can already see.
|
|
187
|
+
*
|
|
188
|
+
* Never before step 3, never once a 2nd-degree or company scan is in (that
|
|
189
|
+
* network's owner is past onboarding), never while a scan runs, and never to
|
|
190
|
+
* someone who has picked a sector (Scores → Your sector) or answered or
|
|
191
|
+
* skipped it before (`fieldAsked`, saved either way). So whoever has only
|
|
192
|
+
* their 1st degree mapped, someone from before this moved included, is asked
|
|
193
|
+
* once. `status` is GET /api/scraper's answer (null while it loads), `settings`
|
|
194
|
+
* GET /api/settings' `settings` (undefined while it loads, null when it
|
|
195
|
+
* couldn't be read). null means not known yet: the page leaves the question
|
|
196
|
+
* out until it is, rather than show it and take it away.
|
|
115
197
|
*/
|
|
116
198
|
export function askForField(status, settings) {
|
|
117
199
|
if (!status) return null;
|
|
118
200
|
const n = status.network;
|
|
119
201
|
// No counts means the app couldn't be asked; a scan running is past asking.
|
|
120
|
-
if (!n || n.first || n.second || n.third || status.running) return false;
|
|
202
|
+
if (!n || !n.first || n.second || n.third || status.running) return false;
|
|
121
203
|
if (settings === undefined) return null;
|
|
122
204
|
return Boolean(settings && !settings.fieldAsked && !settings.sectorFocus?.sectors?.length);
|
|
123
205
|
}
|