The XHR interceptor baked _MOCK_TRAIN at page load time, so seeking or
advancing a playback frame only changed React state but had no effect
on the already-loaded WebView page.
- webviewXhrInterceptor.ts:
- Expose window.__jrsMockUpdateTrain(newData) from inside the IIFE so
the _MOCK_TRAIN variable can be updated after page load
- Add generateMockUpdateScript(trainPositions) helper: calls
__jrsMockUpdateTrain with fresh data + GetDateTime, then calls
window.setReload() to trigger the page's own redraw cycle
- WebView.tsx:
- Import generateMockUpdateScript
- Add useEffect watching mockTrainPositions (skips first mount since
beforeContentLoaded already has correct data)
- When mockApiFeatureEnabled && mockTrainPositions changes, inject the
update script via webview.current.injectJavaScript()
Result: seeking, prev/next frame, and auto-advance during playback now
immediately update the train position display in the WebView.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Crash fix (React Hooks violation):
- PlaybackTimeline.tsx: move ALL hooks (useRef for PanResponder, useCallback)
to before the early return; use seekRef/totalRef to share values into
PanResponder handlers without stale closure issues
Multiple recordings support:
- trainRecorder.ts: redesign to id-based multi-recording system
- RecordingMeta type for lightweight list (no snapshots)
- TrainRecording now includes id field
- saveRecording/loadRecordingById/deleteRecordingById/loadRecordingList
- migrateOldRecording() migrates old single MOCK_RECORDING key on first launch
- constants/storage.ts: add MOCK_RECORDINGS_INDEX + MOCK_RECORDING_DATA_PREFIX keys
- useTrainMenu: savedRecording → recordingList (RecordingMeta[]) + activeRecording (TrainRecording|null)
- startPlayback(id) loads full recording on demand
- deleteRecording(id) deletes by id and refreshes list
- stopPlayback clears activeRecording
- DataSourceSettings: recording list UI
- shows all recordings with date/time, snapshot count, duration
- ▶ play and 削除 buttons per row
- recording/playing status indicator
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements snapshot-based recording and playback of live train
position data for mock API debugging (admin-only).
Recording:
- startRecording() disables mock mode and begins capturing live
train snapshots every ~15s (via getCurrentTrain polling)
- Each snapshot stores { t: elapsed_ms, trains: TrainEntry[] }
- stopRecording() saves completed recording to AsyncStorage
Playback:
- startPlayback() enables mock mode and loops through snapshots
at their original recorded timing using useEffect+setTimeout
- stopPlayback() returns to idle
Storage:
- Single slot in AsyncStorage (MOCK_RECORDING key)
- Loaded on app start in useTrainMenu
- deleteRecording() removes it
New files:
- lib/mockApi/trainRecorder.ts — TrainRecording/TrainSnapshot types
+ save/load/delete AsyncStorage helpers
Modified files:
- constants/storage.ts — add MOCK_RECORDING key
- stateBox/useTrainMenu.tsx — recorder state + all controls
- stateBox/useCurrentTrain.tsx — call addTrainSnapshot after live fetch
- components/Settings/DataSourceSettings.tsx — record/play UI with
status dot, snapshot count, and action buttons
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The separate runtime MOCK switch on the map screen is removed.
Now the admin-only settings toggle (MOCK_API_FEATURE_ENABLED) is the
single control point — turning it on activates mock mode immediately,
turning it off deactivates it.
- useTrainMenu: remove mockApiEnabled/setMockApiEnabled state;
mockApiConfig now derives from mockApiFeatureEnabled alone
- WebView: use mockApiFeatureEnabled for key prop (triggers reload)
- Apps.tsx: remove MockApiToggle import and JSX usage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 100ms pure-debounce approach caused a visible layout flash on every
render (normal mode too) because Tokyo UX was not applied for 100ms.
Fix: call setStrings() immediately in the MutationObserver callback to
prevent any flash, AND keep a 250ms delayed follow-up call to catch
async train re-renders from mock XHR setTimeout(0) responses.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: setStrings() was called synchronously in setReload(),
but the mock XHR interceptor responds via setTimeout(0), so the
site re-rendered train elements AFTER setStrings() ran, stripping
Tokyo UX styling.
Two-pronged fix:
1. setReload() now calls setStrings() again after 200ms to
re-apply Tokyo UX once the async re-render completes.
2. textInsert MutationObserver now watches with subtree:true
(childList only, not attributes - no infinite loop risk)
with 100ms debouncing, so ANY train element re-render
(including those triggered by mock XHR data) will trigger
a re-application of Tokyo UX.
- currentLines postMessage now only fires when direct
children of #disp change (line selection changes only)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two bugs caused the Tokyo UX to be stripped/broken when mock mode was ON:
1. **Double callback firing**: the interceptor's `setTimeout` called both
`onreadystatechange.call(self)` AND `dispatchEvent(new Event('readystatechange'))`.
Since `dispatchEvent` already fires `onXxx` property handlers via the DOM event
model, the page's callback fired twice, causing a second DOM re-render that could
overwrite Tokyo UX modifications. Fixed by relying on `dispatchEvent` only (with
`ProgressEvent` for load events), with a direct-call fallback only for environments
that lack `dispatchEvent`.
2. **No double-injection guard**: if `injectedJavaScriptBeforeContentLoaded` caused
the interceptor to run more than once (e.g., `onPageStarted` fires multiple times
on Android), `_origOpen` would capture the already-patched version, leading to
unexpected behaviour. Fixed by adding `if (window.__jrsMockActive) return;` at the
top of the interceptor IIFE.
3. **Belt-and-suspenders injection**: the interceptor is now also prepended to
`injectedJavaScript` (Tokyo UX script) via the restored `mockApiConfig` parameter
on `injectJavascriptData`. The `__jrsMockActive` guard ensures it's a no-op when
`injectedJavaScriptBeforeContentLoaded` already ran it, but guarantees the
interceptor is active on platforms where IJBCL is unreliable — before
`MoveDisplayStation` triggers the first train-data poll via `onLoadEnd`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the MockXHR wrapper class with direct prototype patching.
Root cause of broken page:
1. CRITICAL: callbacks called without .call(self) → 'this' inside
onload/onreadystatechange was the global object, so this.responseText
returned undefined and the page's JSON.parse crashed silently
2. responseType='json' not handled → page received string instead of
parsed object when using xhr.response
3. instanceof XMLHttpRequest returned false, potentially breaking page
validation logic
New approach (prototype-patch):
- Save original XMLHttpRequest.prototype.open and .send
- Patch open() to detect mock URL and store mock body on the instance
- Patch send() to define instance-level property getters (status,
responseText, response, readyState) and fire callbacks with correct
'this' context via .call(self)
- responseType='json' handled: response getter returns parsed object
- instanceof XMLHttpRequest always true (prototype chain untouched)
- dispatchEvent fires load/readystatechange for addEventListener users
- Non-mock requests delegate to original open/send unchanged
All 6 test cases pass: onload, onreadystatechange, addEventListener,
responseType=json, instanceof, and non-mock pass-through
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- components/Apps/WebView.tsx:
- import mockApiEnabled from context
- add mockApiEnabled to WebView key so toggling forces a full reload
(injectedJavaScript only runs at page load, so reload is required)
- lib/mockApi/webviewXhrInterceptor.ts:
- Fix callback timing bug: callbacks set before open() were stored on
_orig (because _mockBody was null), never on self._onload, causing
mock send() to fire no-op callbacks
- Always store all callbacks/listeners in _callbacks/_listeners maps
- Apply _callbacks to _orig at send() time when not mocked
- Fix addEventListener: always buffer in _listeners, apply to _orig
at send() time; add removeEventListener support
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Scrape & analyse all 19 internal APIs of train.jr-shikoku.co.jp/sp.html
using puppeteer + Chromium remote debugging
- Save live-captured sample JSON for every API endpoint under
lib/mockApi/mockData/ (station lists, lang, timetable, train positions, etc.)
- Add lib/mockApi/webviewXhrInterceptor.ts – generates a JS snippet that
overrides XMLHttpRequest inside the WebView before page scripts run;
intercepts /g?arg1=train&arg2=train (and optionally all static APIs)
returning mock data instead of the real server response
- Add lib/mockApi/index.ts – convenience re-exports + MOCK_TRAIN_POSITIONS
constant pre-populated with captured sample data
- Extend InjectJavascriptOptions with optional mockApiConfig field
- injectJavascriptData() prepends the interceptor JS when mockApiConfig is set
- TrainMenuContext gains mockApiEnabled / setMockApiEnabled and
mockTrainPositions / setMockTrainPositions state; consumers can enable
mock mode and inject custom TrainEntry[] without reloading the app
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Implemented ExpoLiveActivity module for Android to manage live notifications.
- Added foreground service for train tracking and station locking notifications.
- Updated app permissions to include POST_NOTIFICATIONS.
- Enhanced FixedStation and FixedTrain components to support live notifications.
- Introduced new notification builders for train and station activities.
- Updated useCurrentTrain and useNotifications hooks to manage live notification state.
- Added notification channel for live tracking in Android.
- Add regionCode (byte[15]) to history entry in Android/iOS native code
- areaCode = regionCode >> 6 determines the transit area (0-3)
- stationId = (areaCode<<16) | (lineCode<<8) | stationCode
- Add lib/felicaStationMap.ts with 5900+ station entries from
metrodroid/felica_stations.db3 (GPL-3.0)
- FelicaHistoryPage now shows station names instead of raw L/S codes
- Falls back to raw code format if station is not in the database
- Introduced useElesite hook for managing elesite data and settings.
- Added elesite logo asset.
- Updated types to include elesite data structures.
- Enhanced TrainMenuProvider to manage elesite usage settings.
- Implemented data fetching and caching for elesite information.
- Added utility functions to retrieve train information from elesite data.