Commit Graph

10 Commits

Author SHA1 Message Date
Sravan 59310419ba fix(ci): share-extension injector looked one dir too high for the xcodeproj
Codemagic log: "xcodeproj not found at /Users/builder/clone/ios/App/App.xcodeproj"
— missing the `mobile/` segment. The script lives in mobile/scripts, so __dir__ is
mobile/scripts and `File.expand_path('../..', __dir__)` resolved to the REPO ROOT,
not mobile/. The project is at mobile/ios/App/App.xcodeproj. Changed to '..' so ROOT
= mobile, which fixes PROJECT, SRC_DIR, APP_DIR and the entitlements path together.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:28:20 +05:30
Sravan 0a2c7376b9 feat(share): "Biz Connect" in the iOS share sheet — share a photo/file into a chat
Adds the reverse direction: share FROM Photos/Files/Safari INTO a Biz Connect
conversation. An app can only appear in the iOS share sheet as an app-extension
target, so this is real native work, not a web change.

Pieces:
- mobile/ios-share/ShareViewController.swift: a UI-less Share Extension. It stages
  the shared items into the App Group container and opens bizconnect://share. It
  deliberately does NOT reimplement the chat picker — that lives in the app, which
  already has the chat list, search and upload progress. Appends to the manifest
  (never overwrites), so sharing twice before opening the app loses nothing.
- mobile/scripts/add-share-extension.rb: injects the extension target into the
  Capacitor-generated Xcode project on every CI build (Codemagic checks out fresh),
  using the xcodeproj gem that ships with CocoaPods. Embeds it, sets the bundle id
  <app>.share, and MERGES the App Group into the app's entitlements rather than
  clobbering them (push's aps-environment must survive). Idempotent.
- mobile/plugins/share-inbox: getPending()/clear() to read that manifest — the App
  Group container isn't one of Filesystem's known directories, so it needs a bridge.
- home.html: on bizconnect://share (and every resume, and cold-launch), read the
  inbox and show a "Send to…" picker over the chat list; chosen files run the SAME
  upload + /api/messages send as an in-app attachment. Reuses convertFileSrc to read
  the staged bytes with no base64 marshalling.
- ios-patch.sh registers the bizconnect URL scheme; codemagic.yaml fetches a profile
  for the .share bundle id too.

One-time manual gate (CI cannot toggle App capabilities): the App Group
group.com.bizgaze.connect must be created and enabled on both App IDs in the Apple
portal — documented in mobile/IOS_SETUP.md. Without it the two processes can't see
each other's files and sharing silently no-ops; everything else still works.

Validated cross-file: pod-name/jsName/method wiring for all three plugins, App
Group id identical in all 4 files, URL scheme consistent across extension/plist/web,
entitlement-merge preserves push. Needs a new iOS build (new targets + plugins).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 22:58:08 +05:30
Sravan cb81dd6106 feat(storage): Biz Connect folder in Files, manage storage, videos save once
Three connected pieces, so downloads stop being write-only.

1. A folder the user can actually find. ios-patch.sh now sets UIFileSharingEnabled
   and LSSupportsOpeningDocumentsInPlace, and downloads go to the app's Documents
   folder in typed subfolders. iOS shows it as:
       Files -> On My iPhone -> Biz Connect -> Images | Videos | Files
   Previously everything was written to CACHE (private, and iOS purges it whenever
   it likes) and pushed straight at the share sheet, so nothing was ever really
   "kept" by the app.

2. Manage storage (Settings -> Storage). Lists what this device has downloaded,
   grouped by type with per-group and total sizes; each row can be shared to the OS
   sheet (this is where "Save to Photos" now lives) or deleted. Plus Delete all.
   Deleting removes ONLY the local copy — the attachment stays on the server, so
   anything deleted can be downloaded again from the chat.

3. A downloaded video never downloads twice. Images and files have their own
   download link, but a video's tile IS the player, so it had no control at all and
   re-streamed on every play. It now carries a download button; once saved, the
   button becomes a tick and the tile plays from the local file — no network.

The index is treated as a cache of the filesystem, never as truth, because the user
can delete these from the Files app behind our back: every listing re-stats and
forgets what is gone, the library is reconciled at startup, and a local file that
has vanished by play time falls straight back to streaming instead of showing a
broken player.

Unit-checked the path allocator: collisions between different attachments with the
same filename resolve to "name (2)", re-downloading the SAME attachment reuses its
path, and path traversal / illegal characters are neutralised.

Note: the folder and the save location need a new iOS build to take effect. The web
side degrades cleanly — none of this UI appears outside the native app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 15:29:29 +05:30
Sravan b54ba10c69 fix(ios audio): pin .voiceChat before override(.none) so earpiece is reachable
Root cause (WebKit source MediaSessionManagerCocoa.mm + Apple DTS, confirmed by
telemetry): WKWebView's WebRTC re-pins the session to mode .videoChat while capture
is active, and .videoChat auto-implies .defaultToSpeaker. So override(.none) reverts
to the mode default = LOUDSPEAKER, and .none alone can never reach the earpiece once
WebKit flips the mode. Our first override won only because .voiceChat was still active.

Fix: for earpiece, setMode(.voiceChat) (its default route IS the receiver) before
override(.none) in both setSpeaker and the debounced route-change re-assert. Add an
accessory guard so a connected BT/wired headset isn't yanked to the built-in receiver.
Reconcile the launch patch: drop .defaultToSpeaker from inject-audio.js so it stops
contradicting the plugin. native marker 1.0.4-mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 22:31:14 +05:30
Sravan c6f236ecf9 speaker: make AudioRoute a real Capacitor plugin PACKAGE so it registers (device log: has:0)
Device telemetry proved the app-embedded AudioRoute class never registered (not in
Capacitor.Plugins) — appending a CAPBridgedPlugin to AppDelegate.swift gets stripped/undiscovered
in release builds. The plugins that DO register (Share, Camera, Filesystem) are all npm packages
wired by cap sync. So AudioRoute is now a local plugin package (mobile/plugins/audio-route,
file: dep in mobile/package.json) with a podspec + CAPBridgedPlugin Swift — cap sync adds its pod
and Capacitor registers it like the others. load() sets the launch speaker default; setSpeaker({on})
overrides the output port. inject-audio.js no longer injects the plugin class (would duplicate);
it keeps only the AppDelegate launch default as a fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 17:06:16 +05:30
Sravan 90d49d29d0 speaker: native AudioRoute plugin for real earpiece<->speaker toggle on iOS (batch144)
- ios-patch.sh now injects an AudioRoutePlugin (CAPBridgedPlugin, Capacitor 7 auto-registers it)
  into AppDelegate.swift with setSpeaker({on}) -> AVAudioSession.overrideOutputAudioPort. Tolerant/
  build-safe: if it doesn't register, the web call just no-ops (can't crash or fail the build).
- web: nativeAudioRoute()/bzApplyRoute() drive the plugin; toggleSpeakerphone + the on-join/on-tap
  unlock now actually switch the route on iOS (setSinkId can't). canRouteAudio() shows the toggle
  when the native plugin is present. Dormant until the next Codemagic build ships the plugin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:31:25 +05:30
Sravan 134cb8999d ios: default in-call audio to the loudspeaker (AVAudioSession) at build time
Calls were routing to the quiet earpiece. ios-patch.sh now runs a Node helper (Node 20 is
already in the build env) that injects an AVAudioSession .playAndRecord/.voiceChat category with
.defaultToSpeaker + Bluetooth into the generated AppDelegate. Tolerant: exits 0 and no-ops if the
template differs, so it can never fail the Codemagic build. Verified locally against the Cap 7
AppDelegate template — injects correctly and is idempotent. First pass; if WebRTC re-grabs the
session mid-call on device, a follow-up plugin will re-assert .overrideOutputAudioPort(.speaker).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:18:52 +05:30
Sravan 13b0f910ab ci(ios): set CFBundleVersion to Codemagic BUILD_NUMBER so each upload is unique
App Store Connect rejected the upload: 'The bundle version must be higher than the
previously uploaded version: 1'. Capacitor defaults CFBundleVersion to 1, so every
build collided with the first upload. Patch it to the monotonic BUILD_NUMBER.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:09:51 +05:30
Sravan 2e4e0210b0 ci(ios): internal-testing-only publishing + declare encryption exemption
The IPA now builds, signs, uploads and processes on App Store Connect. Set
submit_to_testflight=false so the build stays green via internal testing; external
beta review (which needs Test Information + a demo login) is opt-in later. Also set
ITSAppUsesNonExemptEncryption=false in Info.plist so ASC stops prompting for export
compliance on every upload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:02:28 +05:30
Sravan bc909cb0c0 build(ios): Codemagic pipeline + runbook for App Store (no Mac needed)
The iOS app is a Capacitor shell over the live web UI — the web/server side is already fully
Capacitor-ready (nativePlatform() detects Capacitor; setupNativePush registers APNs tokens via
/api/v1/devices; the APNs sender is built into server/push.js, config-gated). So this adds only
the build/sign/upload path:

- codemagic.yaml: macOS-cloud workflow that generates the iOS project, patches Info.plist,
  generates icons/splash, signs via an App Store Connect API key (automatic signing), archives,
  and uploads to TestFlight. No Mac required.
- mobile/scripts/ios-patch.sh: adds the App-Review privacy usage strings (camera/mic/photos) +
  display name to the generated Info.plist.
- mobile/IOS_SETUP.md: click-by-click runbook — ASC app record, API key, Codemagic integration,
  first build, APNs key → server .env, and the public-submission checklist.

Bundle id com.bizgaze.connect. No secrets committed — Apple keys live in Codemagic + server .env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:45:10 +05:30