414 lines
13 KiB
JavaScript
414 lines
13 KiB
JavaScript
(function (context) {
|
|
|
|
"use strict";
|
|
|
|
context.JK = context.JK || {};
|
|
|
|
// Incremental web-client bridge shim.
|
|
//
|
|
// Phase 1: delegate to FakeJamClient so the install seam can be exercised in
|
|
// forced-native browser mode without changing behavior.
|
|
// Phase 2+: replace selected methods with real WebRTC / websocket-gateway /
|
|
// REST-backed implementations while preserving the jamClient API surface.
|
|
context.JK.WebJamClient = function (app, p2pMessageFactory) {
|
|
var logger = context.JK.logger || console;
|
|
var inner = new context.JK.FakeJamClient(app, p2pMessageFactory);
|
|
var self = this;
|
|
|
|
var state = {
|
|
app: app,
|
|
inSessionPage: false,
|
|
inSession: false,
|
|
currentSessionId: null,
|
|
sessionEventCallbackName: "",
|
|
sessionJoinLeaveCallbackName: "",
|
|
recordingCallbacks: null,
|
|
connectionStatusRefreshRateMs: 0,
|
|
localMedia: {
|
|
status: "idle", // idle | requesting | active | failed
|
|
stream: null,
|
|
error: null,
|
|
autoStartEnabled: false
|
|
}
|
|
};
|
|
|
|
// TEMP/diagnostic recorder for manual capture runs. Disabled by default.
|
|
// Enable with either:
|
|
// localStorage['jk.webClient.recording'] = '1'
|
|
// or window.JK_WEB_CLIENT_RECORDING_ENABLED = true
|
|
var recorder = {
|
|
enabled: false,
|
|
events: [],
|
|
maxEvents: 5000
|
|
};
|
|
|
|
logger.info("*** Web JamClient shim initialized (delegating to FakeJamClient). ***");
|
|
logger.info("*** Web JamClient shim mode: delegated fake + incremental overrides ***");
|
|
|
|
// Copy all enumerable members from the fake client instance so existing code
|
|
// can call methods/properties directly on this object.
|
|
Object.keys(inner).forEach(function(key) {
|
|
var value = inner[key];
|
|
|
|
if (typeof value === "function") {
|
|
this[key] = value.bind(inner);
|
|
} else {
|
|
this[key] = value;
|
|
}
|
|
}, this);
|
|
|
|
function nowIso() {
|
|
return (new Date()).toISOString();
|
|
}
|
|
|
|
function readLocalStorageFlag(key) {
|
|
try {
|
|
if (!context.localStorage) { return false; }
|
|
return context.localStorage.getItem(key) === "1";
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function recorderEnabledByDefault() {
|
|
return !!context.JK_WEB_CLIENT_RECORDING_ENABLED ||
|
|
readLocalStorageFlag("jk.webClient.recording") ||
|
|
!!(context.gon && context.gon.web_client_recording_enabled);
|
|
}
|
|
|
|
function safeSerialize(value) {
|
|
try {
|
|
return JSON.parse(JSON.stringify(value));
|
|
} catch (e) {
|
|
return "[unserializable]";
|
|
}
|
|
}
|
|
|
|
function pushRecord(event) {
|
|
if (!recorder.enabled) { return; }
|
|
event = event || {};
|
|
event.ts = event.ts || nowIso();
|
|
recorder.events.push(event);
|
|
if (recorder.events.length > recorder.maxEvents) {
|
|
recorder.events.shift();
|
|
}
|
|
}
|
|
|
|
function instrumentThenable(methodName, result) {
|
|
if (!result || typeof result.then !== "function") {
|
|
return result;
|
|
}
|
|
|
|
try {
|
|
result.then(function(resolved) {
|
|
pushRecord({
|
|
kind: "jamClientCallResolved",
|
|
method: methodName,
|
|
result: safeSerialize(resolved)
|
|
});
|
|
return resolved;
|
|
}, function(rejected) {
|
|
pushRecord({
|
|
kind: "jamClientCallRejected",
|
|
method: methodName,
|
|
error: safeSerialize(rejected)
|
|
});
|
|
return rejected;
|
|
});
|
|
} catch (e) {
|
|
pushRecord({
|
|
kind: "jamClientInstrumentationError",
|
|
method: methodName,
|
|
error: (e && e.message) ? e.message : String(e)
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function invokeDelegate(methodName, argsLike) {
|
|
var fn = inner[methodName];
|
|
if (typeof fn !== "function") {
|
|
return undefined;
|
|
}
|
|
|
|
var args = Array.prototype.slice.call(argsLike || []);
|
|
pushRecord({
|
|
kind: "jamClientCall",
|
|
method: methodName,
|
|
args: safeSerialize(args)
|
|
});
|
|
|
|
try {
|
|
var result = fn.apply(inner, args);
|
|
|
|
if (!result || typeof result.then !== "function") {
|
|
pushRecord({
|
|
kind: "jamClientCallReturn",
|
|
method: methodName,
|
|
result: safeSerialize(result)
|
|
});
|
|
}
|
|
|
|
return instrumentThenable(methodName, result);
|
|
} catch (e) {
|
|
pushRecord({
|
|
kind: "jamClientCallThrow",
|
|
method: methodName,
|
|
error: (e && e.message) ? e.message : String(e)
|
|
});
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function shouldAutoStartLocalMedia() {
|
|
return !!(context.gon && context.gon.web_client_media_autostart) ||
|
|
!!context.JK_WEB_CLIENT_MEDIA_AUTOSTART ||
|
|
readLocalStorageFlag("jk.webClient.mediaAutoStart");
|
|
}
|
|
|
|
function startLocalMediaIfEnabled() {
|
|
if (!context.navigator || !context.navigator.mediaDevices || !context.navigator.mediaDevices.getUserMedia) {
|
|
state.localMedia.status = "failed";
|
|
state.localMedia.error = "getUserMedia_unavailable";
|
|
pushRecord({kind: "webClientMediaStatus", status: state.localMedia.status, error: state.localMedia.error});
|
|
return;
|
|
}
|
|
|
|
if (!shouldAutoStartLocalMedia()) {
|
|
state.localMedia.autoStartEnabled = false;
|
|
return;
|
|
}
|
|
|
|
if (state.localMedia.status === "requesting" || state.localMedia.status === "active") {
|
|
return;
|
|
}
|
|
|
|
state.localMedia.autoStartEnabled = true;
|
|
state.localMedia.status = "requesting";
|
|
state.localMedia.error = null;
|
|
pushRecord({kind: "webClientMediaStatus", status: state.localMedia.status});
|
|
|
|
context.navigator.mediaDevices.getUserMedia({audio: true, video: false})
|
|
.then(function(stream) {
|
|
state.localMedia.stream = stream;
|
|
state.localMedia.status = "active";
|
|
state.localMedia.error = null;
|
|
pushRecord({
|
|
kind: "webClientMediaStatus",
|
|
status: state.localMedia.status,
|
|
tracks: stream && stream.getAudioTracks ? stream.getAudioTracks().length : 0
|
|
});
|
|
})
|
|
.catch(function(err) {
|
|
state.localMedia.stream = null;
|
|
state.localMedia.status = "failed";
|
|
state.localMedia.error = (err && err.name) ? err.name : String(err);
|
|
pushRecord({
|
|
kind: "webClientMediaStatus",
|
|
status: state.localMedia.status,
|
|
error: state.localMedia.error
|
|
});
|
|
});
|
|
}
|
|
|
|
function stopLocalMedia() {
|
|
var stream = state.localMedia.stream;
|
|
if (stream && stream.getTracks) {
|
|
stream.getTracks().forEach(function(track) {
|
|
try {
|
|
track.stop();
|
|
} catch (e) {}
|
|
});
|
|
}
|
|
state.localMedia.stream = null;
|
|
if (state.localMedia.status === "active" || state.localMedia.status === "requesting") {
|
|
state.localMedia.status = "idle";
|
|
}
|
|
pushRecord({kind: "webClientMediaStatus", status: state.localMedia.status});
|
|
}
|
|
|
|
function installInstrumentationWrappers(target) {
|
|
Object.keys(target).forEach(function(key) {
|
|
var original = target[key];
|
|
if (typeof original !== "function") { return; }
|
|
if (key.indexOf("__") === 0) { return; }
|
|
if (key === "WebClientRecorderEnable" || key === "WebClientRecorderDisable" ||
|
|
key === "WebClientRecorderClear" || key === "WebClientRecorderGetLog" ||
|
|
key === "GetWebClientDebugState" || key === "IsWebClient") {
|
|
return;
|
|
}
|
|
if (key === "RegisterSessionJoinLeaveRequestCallBack" ||
|
|
key === "RegisterRecordingCallbacks" ||
|
|
key === "SessionRegisterCallback" ||
|
|
key === "SessionSetConnectionStatusRefreshRate" ||
|
|
key === "SessionPageEnter" ||
|
|
key === "SessionPageLeave" ||
|
|
key === "JoinSession" ||
|
|
key === "LeaveSession") {
|
|
return;
|
|
}
|
|
if (original.__jkWebClientInstrumented) { return; }
|
|
|
|
target[key] = function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
pushRecord({
|
|
kind: "jamClientCall",
|
|
method: key,
|
|
args: safeSerialize(args)
|
|
});
|
|
|
|
try {
|
|
var result = original.apply(target, args);
|
|
if (!result || typeof result.then !== "function") {
|
|
pushRecord({
|
|
kind: "jamClientCallReturn",
|
|
method: key,
|
|
result: safeSerialize(result)
|
|
});
|
|
}
|
|
return instrumentThenable(key, result);
|
|
} catch (e) {
|
|
pushRecord({
|
|
kind: "jamClientCallThrow",
|
|
method: key,
|
|
error: (e && e.message) ? e.message : String(e)
|
|
});
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
target[key].__jkWebClientInstrumented = true;
|
|
});
|
|
}
|
|
|
|
// Explicit markers for new mode detection/instrumentation.
|
|
this.IsWebClient = function() {
|
|
return true;
|
|
};
|
|
|
|
// Conservative behavior for now:
|
|
// keep reporting "not native" until the web shim is ready to satisfy more
|
|
// native-only expectations. `gon.isNativeClient` remains the feature-mode flag.
|
|
this.IsNativeClient = function() {
|
|
return false;
|
|
};
|
|
|
|
this.GetWebClientDebugState = function() {
|
|
return {
|
|
mode: "delegated-fake",
|
|
state: {
|
|
inSessionPage: state.inSessionPage,
|
|
inSession: state.inSession,
|
|
currentSessionId: state.currentSessionId,
|
|
sessionEventCallbackName: state.sessionEventCallbackName,
|
|
sessionJoinLeaveCallbackName: state.sessionJoinLeaveCallbackName,
|
|
connectionStatusRefreshRateMs: state.connectionStatusRefreshRateMs,
|
|
localMedia: {
|
|
status: state.localMedia.status,
|
|
error: state.localMedia.error,
|
|
hasStream: !!state.localMedia.stream,
|
|
autoStartEnabled: !!state.localMedia.autoStartEnabled
|
|
}
|
|
},
|
|
recorder: {
|
|
enabled: recorder.enabled,
|
|
eventCount: recorder.events.length
|
|
}
|
|
};
|
|
};
|
|
|
|
// ---- Recorder controls (TEMP / manual validation support) ----
|
|
this.WebClientRecorderEnable = function() {
|
|
recorder.enabled = true;
|
|
pushRecord({kind: "webClientRecorder", action: "enable"});
|
|
return true;
|
|
};
|
|
|
|
this.WebClientRecorderDisable = function() {
|
|
pushRecord({kind: "webClientRecorder", action: "disable"});
|
|
recorder.enabled = false;
|
|
return true;
|
|
};
|
|
|
|
this.WebClientRecorderClear = function() {
|
|
recorder.events = [];
|
|
return true;
|
|
};
|
|
|
|
this.WebClientRecorderGetLog = function() {
|
|
return recorder.events.slice();
|
|
};
|
|
|
|
// ---- Incremental method overrides (session lifecycle / callback bookkeeping) ----
|
|
this.RegisterSessionJoinLeaveRequestCallBack = function(callbackName) {
|
|
state.sessionJoinLeaveCallbackName = callbackName || "";
|
|
return invokeDelegate("RegisterSessionJoinLeaveRequestCallBack", arguments);
|
|
};
|
|
|
|
this.RegisterRecordingCallbacks = function() {
|
|
state.recordingCallbacks = Array.prototype.slice.call(arguments);
|
|
return invokeDelegate("RegisterRecordingCallbacks", arguments);
|
|
};
|
|
|
|
this.SessionRegisterCallback = function(callbackName) {
|
|
state.sessionEventCallbackName = callbackName || "";
|
|
return invokeDelegate("SessionRegisterCallback", arguments);
|
|
};
|
|
|
|
this.SessionSetConnectionStatusRefreshRate = function(milliseconds) {
|
|
state.connectionStatusRefreshRateMs = milliseconds || 0;
|
|
return invokeDelegate("SessionSetConnectionStatusRefreshRate", arguments);
|
|
};
|
|
|
|
this.SessionPageEnter = function() {
|
|
state.inSessionPage = true;
|
|
pushRecord({kind: "webClientSessionState", event: "SessionPageEnter", state: safeSerialize(this.GetWebClientDebugState().state)});
|
|
startLocalMediaIfEnabled();
|
|
return invokeDelegate("SessionPageEnter", arguments);
|
|
};
|
|
|
|
this.SessionPageLeave = function() {
|
|
state.inSessionPage = false;
|
|
pushRecord({kind: "webClientSessionState", event: "SessionPageLeave", state: safeSerialize(this.GetWebClientDebugState().state)});
|
|
if (!state.inSession) {
|
|
stopLocalMedia();
|
|
}
|
|
return invokeDelegate("SessionPageLeave", arguments);
|
|
};
|
|
|
|
this.JoinSession = function(sessionDescriptor) {
|
|
state.inSession = true;
|
|
if (sessionDescriptor && typeof sessionDescriptor === "object") {
|
|
state.currentSessionId = sessionDescriptor.sessionID || state.currentSessionId;
|
|
}
|
|
pushRecord({kind: "webClientSessionState", event: "JoinSession", session: safeSerialize(sessionDescriptor)});
|
|
startLocalMediaIfEnabled();
|
|
return invokeDelegate("JoinSession", arguments);
|
|
};
|
|
|
|
this.LeaveSession = function(sessionDescriptor) {
|
|
state.inSession = false;
|
|
state.currentSessionId = null;
|
|
pushRecord({kind: "webClientSessionState", event: "LeaveSession", session: safeSerialize(sessionDescriptor)});
|
|
if (!state.inSessionPage) {
|
|
stopLocalMedia();
|
|
}
|
|
return invokeDelegate("LeaveSession", arguments);
|
|
};
|
|
|
|
// Keep a handle to the delegate for incremental replacement/testing.
|
|
this.__delegate = inner;
|
|
this.__state = state;
|
|
this.__recorder = recorder;
|
|
|
|
recorder.enabled = recorderEnabledByDefault();
|
|
if (recorder.enabled) {
|
|
pushRecord({kind: "webClientRecorder", action: "auto-enable"});
|
|
}
|
|
|
|
installInstrumentationWrappers(this);
|
|
};
|
|
|
|
})(window);
|