mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Implement chrome.cast.Session#leave
This commit is contained in:
@@ -48,7 +48,7 @@ interface CastSession {
|
||||
deviceId: string;
|
||||
appId: string;
|
||||
sessionId?: string;
|
||||
autoJoinContexts: ContentContext[];
|
||||
autoJoinContexts: Set<ContentContext>;
|
||||
}
|
||||
|
||||
/** Creates a cast session object and sets up messaging. */
|
||||
@@ -71,11 +71,11 @@ async function createCastSession(opts: {
|
||||
bridgePort: await bridge.connect(),
|
||||
deviceId: opts.deviceId,
|
||||
appId: opts.appId,
|
||||
autoJoinContexts: []
|
||||
autoJoinContexts: new Set()
|
||||
};
|
||||
|
||||
if (opts.instance.contentContext) {
|
||||
session.autoJoinContexts.push(opts.instance.contentContext);
|
||||
session.autoJoinContexts.add(opts.instance.contentContext);
|
||||
}
|
||||
|
||||
opts.instance.session = session;
|
||||
@@ -150,6 +150,20 @@ function joinSession(instance: CastInstance, session: CastSession) {
|
||||
}
|
||||
}
|
||||
|
||||
function leaveSession(instance: CastInstance) {
|
||||
if (!instance.session?.sessionId) return;
|
||||
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:sessionDisconnected",
|
||||
data: { sessionId: instance.session.sessionId }
|
||||
});
|
||||
|
||||
delete instance.session;
|
||||
if (instance.contentContext?.tabId) {
|
||||
updateActionState(ActionState.Default, instance.contentContext.tabId);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CastInstance {
|
||||
contentPort: AnyPort;
|
||||
contentContext?: ContentContext;
|
||||
@@ -230,11 +244,60 @@ function destroyCastInstance(instance: CastInstance) {
|
||||
activeInstances.delete(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check instance's auto join policy against a content context to
|
||||
* determine if it's a valid auto join target.
|
||||
*/
|
||||
function isValidAutoJoinContext(
|
||||
instance: CastInstance,
|
||||
context: ContentContext
|
||||
) {
|
||||
if (!instance.apiConfig?.autoJoinPolicy) return;
|
||||
|
||||
const { autoJoinPolicy } = instance.apiConfig;
|
||||
if (
|
||||
autoJoinPolicy === AutoJoinPolicy.ORIGIN_SCOPED ||
|
||||
autoJoinPolicy === AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED
|
||||
) {
|
||||
if (context.origin !== instance.contentContext?.origin) return false;
|
||||
if (
|
||||
autoJoinPolicy === AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED &&
|
||||
!isSameContext(context, instance.contentContext)
|
||||
)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
interface AutoJoinTarget {
|
||||
session: CastSession;
|
||||
autoJoinContext: ContentContext;
|
||||
}
|
||||
function findAutoJoinTarget(instance: CastInstance) {
|
||||
for (const [, session] of activeSessions) {
|
||||
if (
|
||||
!session.sessionId ||
|
||||
session.appId !== instance.apiConfig?.sessionRequest.appId
|
||||
)
|
||||
continue;
|
||||
|
||||
for (const context of session.autoJoinContexts) {
|
||||
if (isValidAutoJoinContext(instance, context)) {
|
||||
return { session, autoJoinContext: context } as AutoJoinTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whitelist of safe message types from content. */
|
||||
const allowedContentMessages: Array<Message["subject"]> = [
|
||||
"main:initializeCastSdk",
|
||||
"main:requestSession",
|
||||
"main:requestSessionById",
|
||||
"main:leaveSession",
|
||||
"bridge:sendCastReceiverMessage",
|
||||
"bridge:sendCastSessionMessage"
|
||||
];
|
||||
@@ -298,7 +361,7 @@ const castManager = new (class {
|
||||
if (instance.contentContext?.tabId) {
|
||||
updateActionState(
|
||||
ActionState.Default,
|
||||
instance.contentContext?.tabId
|
||||
instance.contentContext.tabId
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -523,7 +586,7 @@ async function handleContentMessage(instance: CastInstance, message: Message) {
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
case "main:initializeCastSdk":
|
||||
case "main:initializeCastSdk": {
|
||||
instance.apiConfig = message.data.apiConfig;
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:receiverAvailabilityUpdated",
|
||||
@@ -540,43 +603,11 @@ async function handleContentMessage(instance: CastInstance, message: Message) {
|
||||
}
|
||||
|
||||
// Check existing sessions for a valid auto join target
|
||||
sessionLoop: for (const [, session] of activeSessions) {
|
||||
if (
|
||||
!session.sessionId ||
|
||||
session.appId !== instance.apiConfig.sessionRequest.appId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for valid auto join sessions
|
||||
const { autoJoinPolicy } = instance.apiConfig;
|
||||
if (
|
||||
autoJoinPolicy === AutoJoinPolicy.ORIGIN_SCOPED ||
|
||||
autoJoinPolicy === AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED
|
||||
) {
|
||||
for (const context of session.autoJoinContexts) {
|
||||
// Check same origin
|
||||
if (
|
||||
context.origin !== instance.contentContext?.origin
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Check same context for tab scoped
|
||||
if (
|
||||
autoJoinPolicy ===
|
||||
AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED &&
|
||||
!isSameContext(context, instance.contentContext)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
joinSession(instance, session);
|
||||
break sessionLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
const target = findAutoJoinTarget(instance);
|
||||
if (target) joinSession(instance, target.session);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// User has triggered receiver selection via the cast API
|
||||
case "main:requestSession": {
|
||||
@@ -690,12 +721,54 @@ async function handleContentMessage(instance: CastInstance, message: Message) {
|
||||
|
||||
// If requesting by ID, add to the list of auto join contexts
|
||||
if (instance.contentContext) {
|
||||
session.autoJoinContexts.push(instance.contentContext);
|
||||
session.autoJoinContexts.add(instance.contentContext);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:leaveSession": {
|
||||
if (!instance.contentContext || !instance.session?.sessionId) {
|
||||
logger.error("Cannot leave session, instance invalid!");
|
||||
break;
|
||||
}
|
||||
|
||||
// Find auto join target for this instance
|
||||
const target = findAutoJoinTarget(instance);
|
||||
if (target) {
|
||||
// Remove auto join context for future instances
|
||||
instance.session.autoJoinContexts.delete(
|
||||
target.autoJoinContext
|
||||
);
|
||||
|
||||
const sessionAppId = instance.session.appId;
|
||||
leaveSession(instance);
|
||||
|
||||
/**
|
||||
* Disconnect other instances within the scope of this
|
||||
* instances's auto join policy.
|
||||
*/
|
||||
for (const activeInstance of activeInstances) {
|
||||
if (
|
||||
(activeInstance === instance ||
|
||||
activeInstance.session?.appId) !== sessionAppId
|
||||
)
|
||||
continue;
|
||||
|
||||
if (
|
||||
isValidAutoJoinContext(
|
||||
activeInstance,
|
||||
target.autoJoinContext
|
||||
)
|
||||
) {
|
||||
leaveSession(activeInstance);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
leaveSession(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,11 @@ export const SessionSendMessageCallbacks = new WeakMap<
|
||||
Map<string, SendMessageCallback>
|
||||
>();
|
||||
|
||||
export const SessionLeaveSuccessCallback = new WeakMap<
|
||||
Session,
|
||||
Optional<() => void>
|
||||
>();
|
||||
|
||||
/** Creates a Session object and initializes private data. */
|
||||
export function createSession(
|
||||
sessionArgs: ConstructorParameters<typeof Session>
|
||||
@@ -139,6 +144,13 @@ export default class Session {
|
||||
return sendMessageCallback;
|
||||
}
|
||||
|
||||
get #leaveSuccessCallback() {
|
||||
return SessionLeaveSuccessCallback.get(this);
|
||||
}
|
||||
set #leaveSuccessCallback(successCallback: Optional<() => void>) {
|
||||
SessionLeaveSuccessCallback.set(this, successCallback);
|
||||
}
|
||||
|
||||
media: Media[] = [];
|
||||
namespaces: Array<{ name: string }> = [];
|
||||
senderApps: SenderApplication[] = [];
|
||||
@@ -257,10 +269,21 @@ export default class Session {
|
||||
}
|
||||
|
||||
leave(
|
||||
_successCallback?: () => void,
|
||||
_errorCallback?: (err: CastError) => void
|
||||
successCallback?: () => void,
|
||||
errorCallback?: (err: CastError) => void
|
||||
) {
|
||||
logger.info("STUB :: Session#leave");
|
||||
if (!this.sessionId) {
|
||||
errorCallback?.(
|
||||
new CastError(ErrorCode.INVALID_PARAMETER, "Session not active")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.#leaveSuccessCallback = successCallback;
|
||||
|
||||
pageMessaging.page.sendMessage({
|
||||
subject: "main:leaveSession"
|
||||
});
|
||||
}
|
||||
|
||||
loadMedia(
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
|
||||
import Session, {
|
||||
createSession,
|
||||
SessionLeaveSuccessCallback,
|
||||
SessionMessageListeners,
|
||||
SessionSendMessageCallbacks,
|
||||
SessionUpdateListeners
|
||||
@@ -220,8 +221,7 @@ export default class {
|
||||
}
|
||||
|
||||
case "cast:sessionStopped": {
|
||||
const { sessionId } = message.data;
|
||||
const session = this.#sessions.get(sessionId);
|
||||
const session = this.#sessions.get(message.data.sessionId);
|
||||
if (session?.status === SessionStatus.CONNECTED) {
|
||||
session.status = SessionStatus.STOPPED;
|
||||
|
||||
@@ -236,6 +236,24 @@ export default class {
|
||||
break;
|
||||
}
|
||||
|
||||
case "cast:sessionDisconnected": {
|
||||
const session = this.#sessions.get(message.data.sessionId);
|
||||
if (session?.status === SessionStatus.CONNECTED) {
|
||||
session.status = SessionStatus.DISCONNECTED;
|
||||
|
||||
SessionLeaveSuccessCallback.get(session)?.();
|
||||
|
||||
const updateListeners = SessionUpdateListeners.get(session);
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "cast:sessionMessageReceived": {
|
||||
const { sessionId, namespace, messageData } = message.data;
|
||||
const session = this.#sessions.get(sessionId);
|
||||
|
||||
@@ -93,12 +93,14 @@ type ExtMessageDefinitions = {
|
||||
"cast:sessionRequestCancelled": undefined;
|
||||
|
||||
"main:requestSessionById": { sessionId: string };
|
||||
"main:leaveSession": void;
|
||||
|
||||
"cast:instanceCreated": { isAvailable: boolean };
|
||||
"cast:receiverAvailabilityUpdated": { isAvailable: boolean };
|
||||
|
||||
"cast:sessionCreated": CastSessionCreatedDetails & { receiver: Receiver };
|
||||
"cast:sessionUpdated": CastSessionUpdatedDetails;
|
||||
"cast:sessionDisconnected": { sessionId: string };
|
||||
|
||||
/** Allows the selector popup to send cast NS_RECEIVER messages. */
|
||||
"main:sendReceiverMessage": ReceiverSelectorReceiverMessage;
|
||||
|
||||
Reference in New Issue
Block a user