mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-12 10:39:57 +00:00
Implement chrome.cast.Session#leave
This commit is contained in:
@@ -48,7 +48,7 @@ interface CastSession {
|
|||||||
deviceId: string;
|
deviceId: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
autoJoinContexts: ContentContext[];
|
autoJoinContexts: Set<ContentContext>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a cast session object and sets up messaging. */
|
/** Creates a cast session object and sets up messaging. */
|
||||||
@@ -71,11 +71,11 @@ async function createCastSession(opts: {
|
|||||||
bridgePort: await bridge.connect(),
|
bridgePort: await bridge.connect(),
|
||||||
deviceId: opts.deviceId,
|
deviceId: opts.deviceId,
|
||||||
appId: opts.appId,
|
appId: opts.appId,
|
||||||
autoJoinContexts: []
|
autoJoinContexts: new Set()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (opts.instance.contentContext) {
|
if (opts.instance.contentContext) {
|
||||||
session.autoJoinContexts.push(opts.instance.contentContext);
|
session.autoJoinContexts.add(opts.instance.contentContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.instance.session = session;
|
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 {
|
export interface CastInstance {
|
||||||
contentPort: AnyPort;
|
contentPort: AnyPort;
|
||||||
contentContext?: ContentContext;
|
contentContext?: ContentContext;
|
||||||
@@ -230,11 +244,60 @@ function destroyCastInstance(instance: CastInstance) {
|
|||||||
activeInstances.delete(instance);
|
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. */
|
/** Whitelist of safe message types from content. */
|
||||||
const allowedContentMessages: Array<Message["subject"]> = [
|
const allowedContentMessages: Array<Message["subject"]> = [
|
||||||
"main:initializeCastSdk",
|
"main:initializeCastSdk",
|
||||||
"main:requestSession",
|
"main:requestSession",
|
||||||
"main:requestSessionById",
|
"main:requestSessionById",
|
||||||
|
"main:leaveSession",
|
||||||
"bridge:sendCastReceiverMessage",
|
"bridge:sendCastReceiverMessage",
|
||||||
"bridge:sendCastSessionMessage"
|
"bridge:sendCastSessionMessage"
|
||||||
];
|
];
|
||||||
@@ -298,7 +361,7 @@ const castManager = new (class {
|
|||||||
if (instance.contentContext?.tabId) {
|
if (instance.contentContext?.tabId) {
|
||||||
updateActionState(
|
updateActionState(
|
||||||
ActionState.Default,
|
ActionState.Default,
|
||||||
instance.contentContext?.tabId
|
instance.contentContext.tabId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,7 +586,7 @@ async function handleContentMessage(instance: CastInstance, message: Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (message.subject) {
|
switch (message.subject) {
|
||||||
case "main:initializeCastSdk":
|
case "main:initializeCastSdk": {
|
||||||
instance.apiConfig = message.data.apiConfig;
|
instance.apiConfig = message.data.apiConfig;
|
||||||
instance.contentPort.postMessage({
|
instance.contentPort.postMessage({
|
||||||
subject: "cast:receiverAvailabilityUpdated",
|
subject: "cast:receiverAvailabilityUpdated",
|
||||||
@@ -540,43 +603,11 @@ async function handleContentMessage(instance: CastInstance, message: Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check existing sessions for a valid auto join target
|
// Check existing sessions for a valid auto join target
|
||||||
sessionLoop: for (const [, session] of activeSessions) {
|
const target = findAutoJoinTarget(instance);
|
||||||
if (
|
if (target) joinSession(instance, target.session);
|
||||||
!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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// User has triggered receiver selection via the cast API
|
// User has triggered receiver selection via the cast API
|
||||||
case "main:requestSession": {
|
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 requesting by ID, add to the list of auto join contexts
|
||||||
if (instance.contentContext) {
|
if (instance.contentContext) {
|
||||||
session.autoJoinContexts.push(instance.contentContext);
|
session.autoJoinContexts.add(instance.contentContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
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>
|
Map<string, SendMessageCallback>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
export const SessionLeaveSuccessCallback = new WeakMap<
|
||||||
|
Session,
|
||||||
|
Optional<() => void>
|
||||||
|
>();
|
||||||
|
|
||||||
/** Creates a Session object and initializes private data. */
|
/** Creates a Session object and initializes private data. */
|
||||||
export function createSession(
|
export function createSession(
|
||||||
sessionArgs: ConstructorParameters<typeof Session>
|
sessionArgs: ConstructorParameters<typeof Session>
|
||||||
@@ -139,6 +144,13 @@ export default class Session {
|
|||||||
return sendMessageCallback;
|
return sendMessageCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get #leaveSuccessCallback() {
|
||||||
|
return SessionLeaveSuccessCallback.get(this);
|
||||||
|
}
|
||||||
|
set #leaveSuccessCallback(successCallback: Optional<() => void>) {
|
||||||
|
SessionLeaveSuccessCallback.set(this, successCallback);
|
||||||
|
}
|
||||||
|
|
||||||
media: Media[] = [];
|
media: Media[] = [];
|
||||||
namespaces: Array<{ name: string }> = [];
|
namespaces: Array<{ name: string }> = [];
|
||||||
senderApps: SenderApplication[] = [];
|
senderApps: SenderApplication[] = [];
|
||||||
@@ -257,10 +269,21 @@ export default class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leave(
|
leave(
|
||||||
_successCallback?: () => void,
|
successCallback?: () => void,
|
||||||
_errorCallback?: (err: CastError) => 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(
|
loadMedia(
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
|
|
||||||
import Session, {
|
import Session, {
|
||||||
createSession,
|
createSession,
|
||||||
|
SessionLeaveSuccessCallback,
|
||||||
SessionMessageListeners,
|
SessionMessageListeners,
|
||||||
SessionSendMessageCallbacks,
|
SessionSendMessageCallbacks,
|
||||||
SessionUpdateListeners
|
SessionUpdateListeners
|
||||||
@@ -220,8 +221,7 @@ export default class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "cast:sessionStopped": {
|
case "cast:sessionStopped": {
|
||||||
const { sessionId } = message.data;
|
const session = this.#sessions.get(message.data.sessionId);
|
||||||
const session = this.#sessions.get(sessionId);
|
|
||||||
if (session?.status === SessionStatus.CONNECTED) {
|
if (session?.status === SessionStatus.CONNECTED) {
|
||||||
session.status = SessionStatus.STOPPED;
|
session.status = SessionStatus.STOPPED;
|
||||||
|
|
||||||
@@ -236,6 +236,24 @@ export default class {
|
|||||||
break;
|
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": {
|
case "cast:sessionMessageReceived": {
|
||||||
const { sessionId, namespace, messageData } = message.data;
|
const { sessionId, namespace, messageData } = message.data;
|
||||||
const session = this.#sessions.get(sessionId);
|
const session = this.#sessions.get(sessionId);
|
||||||
|
|||||||
@@ -93,12 +93,14 @@ type ExtMessageDefinitions = {
|
|||||||
"cast:sessionRequestCancelled": undefined;
|
"cast:sessionRequestCancelled": undefined;
|
||||||
|
|
||||||
"main:requestSessionById": { sessionId: string };
|
"main:requestSessionById": { sessionId: string };
|
||||||
|
"main:leaveSession": void;
|
||||||
|
|
||||||
"cast:instanceCreated": { isAvailable: boolean };
|
"cast:instanceCreated": { isAvailable: boolean };
|
||||||
"cast:receiverAvailabilityUpdated": { isAvailable: boolean };
|
"cast:receiverAvailabilityUpdated": { isAvailable: boolean };
|
||||||
|
|
||||||
"cast:sessionCreated": CastSessionCreatedDetails & { receiver: Receiver };
|
"cast:sessionCreated": CastSessionCreatedDetails & { receiver: Receiver };
|
||||||
"cast:sessionUpdated": CastSessionUpdatedDetails;
|
"cast:sessionUpdated": CastSessionUpdatedDetails;
|
||||||
|
"cast:sessionDisconnected": { sessionId: string };
|
||||||
|
|
||||||
/** Allows the selector popup to send cast NS_RECEIVER messages. */
|
/** Allows the selector popup to send cast NS_RECEIVER messages. */
|
||||||
"main:sendReceiverMessage": ReceiverSelectorReceiverMessage;
|
"main:sendReceiverMessage": ReceiverSelectorReceiverMessage;
|
||||||
|
|||||||
Reference in New Issue
Block a user