Matthew Meszaros
27630eec0a
feat: add website visitor tracking for issue #255 with migration 000106 (website_tracking_settings, website_visitors, website_page_hits, all registered in the orgtransfer spec), a consent-gated dependency-free tracking.js served by the Rust tracking service with a rate-limited, size-capped, prefetch-filtered POST /p ingest that forwards to a new backend internal page-hits endpoint for server-side user-agent and GeoIP enrichment, contact identification only through the click ticket the redirect appends to registered hosts, a per-workspace retention job, page_hit events with an expandable detail view in the contact Activity timeline, a Settings > Website tracking page for the snippet and consent, location and retention configuration, realtime PAGE_HIT fanout, and a website tracking guide plus endpoint, export and configuration docs
2026-08-29 03:25:50 -07:00
Matthew Meszaros
42ddb78e60
feat: make the realtime service survive a client that spams channel joins, and stop both clients from being that client: ws_join is now spent per phx_join in a shared RealtimeWeb.ChannelGuard that every channel runs BEFORE its Auth lookup (it was only ever spent on the socket handshake, so an established socket could issue unlimited joins and each one cost a Postgres query), handshakes move to their own ws_connect bucket so a reconnect storm cannot eat the budget a client needs to rejoin with, the retry_after_ms hint now points at the next window instead of shrinking as overage grew, every channel's join rejection carries the numeric code the API reference already promised (plus 4005 for a malformed topic), BulkChannel only pushes an event to the user who owns the operation instead of to anyone who guesses the id, and the web and admin clients treat a rate_limited join as transient (wait out the hint, keep the socket) while backing off per-channel rejoins on a decaying schedule rather than reconnecting on the 120ms floor forever; adds an ExUnit suite for realtime and a vitest suite for admin, both wired into CI
2026-08-26 21:14:34 -07:00
Matthew Meszaros
8bd2c2b57a
feat: make self-hosting work end to end and rewrite the guide around what was tested ( #97 )
2026-08-13 09:47:46 +02:00
Matthew Meszaros
bebc09289d
build: cross compile the go images and fix realtime and the healthchecks
2026-07-21 17:34:59 +02:00
Matthew Meszaros
4962765a79
fix: format the org_channel AI_DRAFT access_unibox clause to satisfy mix format --check-formatted (Elixir CI)
2026-07-15 06:47:07 +02:00
Matthew Meszaros
56de65380b
feat: inbox agent that drafts a suggested unibox reply on inbound human replies for human approve/edit/discard - paid + per-org opt-in (organizations.inbox_agent_enabled) feature where the consumer's reply hook, on a non-automated reply, detaches a goroutine (panic-contained, never blocks ingest) that checks entitlement, dedupes via ai_thread_drafts partial unique indexes (one pending per thread, unique source_message_id), pre-checks balance, grounds a reply in the thread history + org voice + skills via generation.Provider.Complete, reserves a draft row then charges 5 credits (idempotency inbox_agent:<draft.ID>, row unwound on a fresh context if the charge fails so no unpaid draft lingers), and emits an org-scoped AI_DRAFT_READY event gated on access_unibox; the agent never sends - only a human POST to /unibox/agent-drafts/:id/approve sends through the normal reply path, claiming pending->approved before send with an approved->pending revert on send failure, alongside list + discard endpoints; plus the unibox awaiting_agent_draft badge + agent_drafts search scope, an AgentDraftCard in the thread view with inline-editable approve-and-send/discard, the workspace settings opt-in toggle, CanUseInboxAgent paid gate, migration 000065, and docs (inbox-agent guide, endpoints, realtime)
2026-07-14 06:13:27 +02:00
Matthew Meszaros
f48d51bf16
feat: AI contact research agent that gathers cited web findings per contact with sync and background-batch runs - contact_research_runs table plus a strict save_research schema (every signal and public_artifact must carry a url, confidence high/medium/low, signals<=5 hooks<=3, reject-and-reprompt once then fail), a text/template runtime prompt injecting org voice, contact record, objective, and 5-search/6-fetch budgets, a research service that drives search_web/fetch_url through the provider loop and charges 2 credits on save (billable even for nothing_found, refund-free since charged only on save, balance and abuse-cap pre-checked so a capped org never does free work), POST/GET /contacts/:id/research plus a 500-cap /contacts/research/batch draining through a bounded pool of 4 workers over FOR-UPDATE-SKIP-LOCKED claims (no new Kafka), an org-scoped AI_RESEARCH_PROGRESS realtime event gated to view_contacts, APIPermAIResearch bit 23, a ContactEdit Research tab and a ContactsTable bulk Research action, and docs; also org-scopes every credit and research idempotency key so a client-supplied Idempotency-Key can never replay across tenants
2026-07-13 19:34:11 +02:00
Matthew Meszaros
262d25779c
feat: realtime admin dashboard + diagnosability - new Elixir admin:platform channel (JWT-only, gated on users.admin_permissions) mirrors every platform event, the admin app connects through a /getaway-tokenized Phoenix socket client with a throttled react-query invalidation spine, a Live Events firehose page streams the platform with filters/pause/JSON expand, a System Status page runs parallel infra probes (postgres/redis/kafka/schema-registry/realtime/tracking via /admin/system/status), and the worker logs viewer gains follow mode, line-count selection, and copy
2026-07-12 09:00:10 +02:00
Matthew Meszaros
15088a6f64
feat: pin cowboy >= 2.16 in the realtime service so CR/LF response header values are rejected before the wire (EEF-CVE-2026-43966/43969 has no patched cowlib release)
2026-07-07 05:56:47 +02:00
Matthew Meszaros
e190571696
feat: harden the live channel with an is_map payload guard, byte-bounded chat/id/resource strings, a per-surface permission gate that strips cursor-chat text, and a websocket max_frame_size cap
2026-07-02 20:33:44 +02:00
Matthew Meszaros
88cabf5cac
feat: cap the live:select ids payload before traversal so an oversized selection list is never fully walked by the realtime channel
2026-07-02 14:31:36 +02:00
Matthew Meszaros
02b25724f4
feat: extend the ephemeral live channel with a sanitized chat field on live:cursor frames and a new live:select kind that broadcasts a capped node-id list for canvas selections
2026-07-02 07:40:06 +02:00
Matthew Meszaros
3ffd4416a2
feat: add a generic live:patch channel kind that fans out a small sanitized scalar map for ephemeral collaborative-state hints
2026-06-30 10:34:14 +02:00
Matthew Meszaros
248c886290
feat: add an ephemeral live:cursor/live:node channel path to the realtime org channel that bypasses the Redis ws limiters and the sequenced resume buffer via an in-process token bucket and broadcast_from fan-out
2026-06-30 10:06:19 +02:00
Matthew Meszaros
f9d5c85a32
fix(security): move realtime sentry onto a Finch HTTP client and drop the hackney dependency to clear CVE-2026-47071
2026-06-28 06:18:33 +00:00
Matthew Meszaros
b83aa51921
feat: make CUSTOM_EVENT fire-event signals an explicit allow on the org channel and return structured {code, reason} errors on a failed channel join
2026-06-14 11:02:12 +02:00
Matthew Meszaros
799e5f10ef
feat: surface WebSocket connection-rejection reasons to the client as a structured {code, reason, retry_after_ms} payload (Discord-style 4003/4004/4007/4009/4010) instead of a bare 403, and fix the rate-limit 3-tuple match
2026-06-14 11:02:12 +02:00
Matthew Meszaros
34e579fc0f
feat: authenticate the realtime WebSocket with OAuth access tokens (wmat_ with the realtime scope), validated against oauth_access_grants alongside wmbly_ API keys and JWTs, and map not-a-member/forbidden rejections to close code 4010
2026-06-14 11:02:12 +02:00
Matthew Meszaros
f2fa0d756c
feat: guarantee in-order gateway delivery via a per-org sequencer pool — serialize each org's events (partitioned by org id) so seq order always equals broadcast order even under concurrent ingest, closing the resume race where a higher seq could arrive first and a disconnect would skip the lower one
2026-06-13 11:31:07 +02:00
Matthew Meszaros
4552ab4e31
feat: make the org realtime channel a resumable gateway — assign a monotonic per-org sequence + buffer each event in a capped Redis stream (Realtime.EventLog) before broadcast, advertise the current seq in the HELLO and stamp it on every event, and replay the missed gap (re-filtered) on reconnect via resume.last_seq or signal a full resync when the buffer no longer covers the client
2026-06-13 11:23:07 +02:00
Matthew Meszaros
c5ced50868
fix: apply mix format to the realtime service (supervisor child-list indentation in application.ex + org_channel.ex) so the Elixir CI format check passes
2026-06-12 17:45:32 +02:00
Matthew Meszaros
7b34b96b37
feat: keep the per-user realtime connection cap at 10 but reap dead connections via a periodic liveness sweep and process pids, so the count reflects real presence and stale sockets never hold a slot
2026-06-12 07:46:13 +02:00
Matthew Meszaros
3cbfc06bc4
feat: add WebSocket intents (selective event-family subscription) and a HELLO-style org join reply advertising heartbeat cadence, and correct realtime docs on connection rejections and at-most-once delivery
2026-06-12 07:17:09 +02:00
Matthew Meszaros
4f1ac8c273
feat: enforce org team-presence privacy in the realtime OrgChannel — gate Presence.track on show_online, strip viewing/editing detail when show_activity is off, and re-track/untrack live on PRESENCE_POLICY_UPDATED
2026-06-12 05:59:01 +02:00
Matthew Meszaros
ba3ab98e1e
feat: bridge realtime events over Redis pub/sub when Google Pub/Sub is unconfigured (RedisBus publisher in backend/consumer plus Realtime.Redis.EventSubscriber and a shared EventBroadcaster) so dashboard live updates and presence-driven collaboration actually fire in local dev
2026-06-12 04:49:58 +02:00
Matthew Meszaros
70064e9e1d
fix: define OrgChannel.handle_out/3 so presence_diff broadcasts are pushed to clients instead of crashing the channel — the missing callback was terminating the org channel and dropping every websocket connection in a reconnect loop
2026-06-11 13:47:11 +02:00
Matthew Meszaros
80d080b982
feat: fix realtime UUID param encoding crashing org-channel joins, remove the permission matrix from Roles & access, compact the role dropdown, and drop the redundant Integrations tab from settings nav
2026-06-11 11:16:13 +02:00
Matthew Meszaros
3473d09bcc
feat: fix review findings in the roles redesign — GetMembers role_id column (members endpoint 500), TransferOwnership role_id hygiene, accept-time role re-resolution, race-free in-use delete guard covering invitations, assignment anti-escalation with self-role-change block, canManage-gated members UI, colored RolePills, fresh currentOrganization on refetch, dev JWT_SECRET wiring for make realtime, docs corrections
2026-06-11 10:45:21 +02:00
Matthew Meszaros
371fe0c0a3
feat: add Phoenix.Presence org collaboration layer (online members, viewing/editing/replying activity) with permission-gated org event fanout in the realtime service
2026-06-11 07:51:34 +02:00
Matthew Meszaros
5b735bb528
feat: replace ascii architecture diagrams in the realtime readme and resources docs with prose and tables
2026-06-10 18:27:42 +02:00
Matthew Meszaros
da82993846
feat: add campaign workflow backend support
...
Add sequence action-node storage and execution, template conditional rendering, lead progress state, profile updates, webhook fan-out throttling, and supporting repository fixes.
2026-06-06 07:49:52 +02:00
Matthew Meszaros
60773b3d8e
feat: make dashboard realtime
2026-05-30 04:17:10 +00:00
Matthew Meszaros
99d2bff0cb
feat: unify dev-mode hot-reload for go, rust, elixir under make dev
2026-05-25 03:21:42 +00:00
Matthew Meszaros
ce870a15b3
ci: fix Elixir formatting / Elixir version / missing pnpm lockfile
...
- realtime/: `mix format` applied; long Logger calls reformatted across
config/runtime/application/connections/user_channel/user_socket/
endpoint. CI's "Check formatting" step now passes.
- CI Elixir bumped from 1.16 → 1.18 (with OTP 27) to match mix.exs's
`~> 1.18` requirement. Phoenix 1.8.7 + plug 1.19 also expect this.
- web/: generate + commit pnpm-lock.yaml so actions/setup-node@v4's
pnpm cache step + `pnpm install --frozen-lockfile` can resolve.
2026-05-23 16:17:27 +00:00
Matthew Meszaros
d8bb10bf8f
ci: fix Bad-credentials + bump deps to clear Trivy CVEs
...
Workflow:
- Add explicit `permissions: contents: read, pull-requests: read`
so dorny/paths-filter can list PR files via the GitHub API. Without
it the "Detect Changes" job dies with "Bad credentials" on PRs and
every downstream language CI gets skipped.
Go:
- google.golang.org/grpc v1.78.0 → v1.79.3 (CVE-2026-33186 — HTTP/2
path validation authorization bypass).
Elixir (realtime):
- cowboy 2.14.2 → 2.15.0 (CVE-2026-8466)
- cowlib 2.16.0 → 2.16.1 (CVE-2026-43970, CVE-2026-7790)
- phoenix 1.8.3 → 1.8.7 (CVE-2026-32689 — long-poll memory blow-up)
- plug 1.19.1 → 1.19.2 (CVE-2026-8468 — multipart header overflow)
- plug_cowboy 2.7.5 → 2.8.1 (CVE-2026-32688 — unauth DoS)
- postgrex 0.22.0 → 0.22.2 (CVE-2026-32687 — channel-name SQLi)
Rust (tracking):
- aws-lc-rs 1.15.4 → 1.17.0 (pulls aws-lc-sys to 0.41.0 — fixes
GHSA-394x-vwmw-crm3, GHSA-65p9-r9h6-22vj, GHSA-9f94-5g5w-gf6r,
GHSA-hfpc-8r3f-gw53, GHSA-vw5v-4f2q-w9xf)
- openssl 0.10.75 → 0.10.80 (CVE-2026-41676/8/81/898, -42327)
- rustls-webpki 0.103.9 → 0.103.13
- Add .trivyignore for GHSA-82j2-j2ch-gfr8 on the old rustls-webpki
0.101.7 path that aws-smithy-http-client / hyper-rustls 0.24 still
pulls in. AWS SDK hasn't migrated to rustls 0.23+ yet; the CRL
parsing path the advisory covers isn't reachable from our usage
(SSM + Secrets Manager at startup over the public CA chain).
2026-05-23 16:13:39 +00:00
Matthew Meszaros
695e2b5a33
fix: contacts crash, campaigns panic, websocket — 4 distinct bugs found while triaging the page-blank symptom
...
1) Contacts crash "c is null":
contactRepository.Search declared `var contacts []models.Contact` so
an empty result set returned a nil slice, which Go marshals as JSON
null. The frontend's flatMap((p) => p.data) over null yields [null],
and the page then accesses c.subscribed → throws. Initialize as
make([]models.Contact, 0, limit+1) so the wire format is always [].
Also defensive on the client: useSearchContacts + useCampaigns now
coerce p.data ?? [] and drop nulls before returning.
2) Campaigns panic on any non-empty result:
campaignRepository.Search allocated `make([]models.Campaign, 0, limit+1)`
(length 0) then did `campaigns[i] = campaign`. That's an
index-out-of-range on the first iteration. Switched to `append`.
Anyone with at least one campaign would see a 500 / blank screen.
3) Websocket "Token expired":
SocketTTL was 60s. The frontend reconnect backoff caps at 30s, so
after a rejected handshake the next attempt could fire 30-60s
later. Combined with rare back-pressure on /getaway the token was
already past exp by the time the realtime saw it. Bumped to 10 min
— short enough to keep the token low-impact, long enough to outlast
the backoff schedule.
4) Websocket "Connection limit exceeded":
Realtime.Connections only untracked on channel terminate, never on
socket disconnect. Sockets that connected and disconnected without
joining a channel leaked. Each reconnect loop bumped the counter
until the per-user limit (10) was hit, after which every legitimate
connect was rejected even after fixing #3 .
Fix: GenServer Process.monitor's the socket pid on track, and
`:DOWN` handler calls do_untrack with the right (user_id, ip).
5) Phoenix protocol mismatch:
Frontend appended vsn=2.0.0 to the WS URL, but sendRaw + joinChannel
send the V1 object format. Realtime's Phoenix.Socket.V2.JSONSerializer
crashed with a badmatch on the first phx_join, killing the socket
right after connect. Switched to vsn=1.0.0 to match what the client
actually emits.
2026-05-23 05:32:28 +00:00
Matthew Meszaros
c12fd93def
Add local Sentry logging
2026-02-14 05:49:49 +01:00
Matthew Meszaros
141bc54974
Add sample auth UI theme
2026-02-10 19:30:47 +01:00
Máté Mészáros (Laptop)
ed35ab2dbc
Realtime Updates
2026-01-30 15:32:58 +01:00
Máté Mészáros (Laptop)
6adb4cdd5a
Organization, Subscription, Inqueries, limits.
2026-01-27 05:55:48 +01:00
Máté Mészáros (Laptop)
5ac159d2f8
Realtime, api keys & more
2026-01-26 16:04:42 +01:00
Máté Mészáros (Laptop)
7ae36cc689
Convert realtime submodule to regular directory
2026-01-26 04:50:21 +01:00
Máté Mészáros (Laptop)
81d5970ea8
Realtime, Task Handler, Worker & Consumer Setup
2026-01-26 04:42:19 +01:00